diff options
Diffstat (limited to 'tools/perf')
186 files changed, 7342 insertions, 2329 deletions
diff --git a/tools/perf/.gitignore b/tools/perf/.gitignore index 782d86e961b9..717221e98450 100644 --- a/tools/perf/.gitignore +++ b/tools/perf/.gitignore @@ -15,6 +15,7 @@ perf.data perf.data.old output.svg perf-archive +perf-with-kcore tags TAGS cscope* 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-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-probe.txt b/tools/perf/Documentation/perf-probe.txt index 1513935c399b..aaa869be3dc1 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -104,6 +104,9 @@ OPTIONS Specify path to the executable or shared library file for user space tracing. Can also be used with --funcs option. +--demangle-kernel:: + Demangle kernel symbols. + In absence of -m/-x options, perf probe checks if the first argument after the options is an absolute path name. If its an absolute path, perf probe uses it as a target module/target user space binary to probe. diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index d2b59af62bc0..0927bf4e6c2a 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -147,7 +147,7 @@ OPTIONS -w:: --column-widths=<width[,width...]>:: Force each column width to the provided list, for large terminal - readability. + readability. 0 means no limit (default behavior). -t:: --field-separator=:: @@ -276,6 +276,9 @@ OPTIONS Demangle symbol names to human readable form. It's enabled by default, disable with --no-demangle. +--demangle-kernel:: + Demangle kernel symbol names to human readable form (for C++ kernels). + --mem-mode:: Use the data addresses of samples in addition to instruction addresses to build the histograms. To generate meaningful output, the perf.data diff --git a/tools/perf/Documentation/perf-timechart.txt b/tools/perf/Documentation/perf-timechart.txt index 5e0f986dff38..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 ----------------- @@ -54,6 +64,19 @@ TIMECHART OPTIONS 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 -------------- @@ -63,6 +86,9 @@ 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 @@ -87,6 +113,14 @@ Record system-wide timechart: $ 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 180ae02137a5..3265b1070518 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -98,6 +98,9 @@ Default is to monitor all CPUS. --hide_user_symbols:: Hide user symbols. +--demangle-kernel:: + Demangle kernel symbols. + -D:: --dump-symtab:: Dump the symbol table used for profiling. @@ -193,6 +196,12 @@ Default is to monitor all CPUS. sum of shown entries will be always 100%. "absolute" means it retains the original value before and after the filter is applied. +-w:: +--column-widths=<width[,width...]>:: + Force each column width to the provided list, for large terminal + readability. 0 means no limit (default behavior). + + INTERACTIVE PROMPTING KEYS -------------------------- 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 45da209b6ed3..344c4d3d0a4a 100644 --- a/tools/perf/MANIFEST +++ b/tools/perf/MANIFEST @@ -37,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 9670a16fa577..262916f4a377 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -126,6 +126,7 @@ PYRF_OBJS = SCRIPT_SH = SCRIPT_SH += perf-archive.sh +SCRIPT_SH += perf-with-kcore.sh grep-libs = $(filter -l%,$(1)) strip-libs = $(filter-out -l%,$(1)) @@ -263,6 +264,7 @@ LIB_H += util/xyarray.h LIB_H += util/header.h LIB_H += util/help.h LIB_H += util/session.h +LIB_H += util/ordered-events.h LIB_H += util/strbuf.h LIB_H += util/strlist.h LIB_H += util/strfilter.h @@ -295,11 +297,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 @@ -345,6 +349,7 @@ LIB_OBJS += $(OUTPUT)util/machine.o LIB_OBJS += $(OUTPUT)util/map.o LIB_OBJS += $(OUTPUT)util/pstack.o LIB_OBJS += $(OUTPUT)util/session.o +LIB_OBJS += $(OUTPUT)util/ordered-events.o LIB_OBJS += $(OUTPUT)util/comm.o LIB_OBJS += $(OUTPUT)util/thread.o LIB_OBJS += $(OUTPUT)util/thread_map.o @@ -373,6 +378,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 @@ -395,6 +402,7 @@ LIB_OBJS += $(OUTPUT)tests/perf-record.o 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/fdarray.o LIB_OBJS += $(OUTPUT)tests/pmu.o LIB_OBJS += $(OUTPUT)tests/hists_common.o LIB_OBJS += $(OUTPUT)tests/hists_link.o @@ -419,6 +427,7 @@ endif endif LIB_OBJS += $(OUTPUT)tests/mmap-thread-lookup.o LIB_OBJS += $(OUTPUT)tests/thread-mg-share.o +LIB_OBJS += $(OUTPUT)tests/switch-tracking.o BUILTIN_OBJS += $(OUTPUT)builtin-annotate.o BUILTIN_OBJS += $(OUTPUT)builtin-bench.o @@ -761,7 +770,7 @@ $(LIBTRACEEVENT)-clean: install-traceevent-plugins: $(LIBTRACEEVENT) $(QUIET_SUBDIR0)$(TRACE_EVENT_DIR) $(LIBTRACEEVENT_FLAGS) install_plugins -LIBAPIKFS_SOURCES = $(wildcard $(LIB_PATH)fs/*.[ch]) +LIBAPIKFS_SOURCES = $(wildcard $(LIB_PATH)fs/*.[ch] $(LIB_PATH)fd/*.[ch]) # if subdir is set, we've been called from above so target has been built # already @@ -871,6 +880,8 @@ install-bin: all install-gtk $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)' $(call QUIET_INSTALL, perf-archive) \ $(INSTALL) $(OUTPUT)perf-archive -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)' + $(call QUIET_INSTALL, perf-with-kcore) \ + $(INSTALL) $(OUTPUT)perf-with-kcore -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)' ifndef NO_LIBPERL $(call QUIET_INSTALL, perl-scripts) \ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/Perf-Trace-Util/lib/Perf/Trace'; \ @@ -916,7 +927,7 @@ config-clean: @$(MAKE) -C config/feature-checks clean >/dev/null clean: $(LIBTRACEEVENT)-clean $(LIBAPIKFS)-clean config-clean - $(call QUIET_CLEAN, core-objs) $(RM) $(LIB_OBJS) $(BUILTIN_OBJS) $(LIB_FILE) $(OUTPUT)perf-archive $(OUTPUT)perf.o $(LANG_BINDINGS) $(GTK_OBJS) + $(call QUIET_CLEAN, core-objs) $(RM) $(LIB_OBJS) $(BUILTIN_OBJS) $(LIB_FILE) $(OUTPUT)perf-archive $(OUTPUT)perf-with-kcore $(OUTPUT)perf.o $(LANG_BINDINGS) $(GTK_OBJS) $(call QUIET_CLEAN, core-progs) $(RM) $(ALL_PROGRAMS) perf $(call QUIET_CLEAN, core-gen) $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo $(OUTPUT)common-cmds.h TAGS tags cscope* $(OUTPUT)PERF-VERSION-FILE $(OUTPUT)PERF-CFLAGS $(OUTPUT)PERF-FEATURES $(OUTPUT)util/*-bison* $(OUTPUT)util/*-flex* $(QUIET_SUBDIR0)Documentation $(QUIET_SUBDIR1) clean diff --git a/tools/perf/arch/arm/tests/dwarf-unwind.c b/tools/perf/arch/arm/tests/dwarf-unwind.c index 9f870d27cb39..62eff847f91c 100644 --- a/tools/perf/arch/arm/tests/dwarf-unwind.c +++ b/tools/perf/arch/arm/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 diff --git a/tools/perf/arch/arm/util/unwind-libunwind.c b/tools/perf/arch/arm/util/unwind-libunwind.c index 729ed69a6664..62c397ed3d97 100644 --- a/tools/perf/arch/arm/util/unwind-libunwind.c +++ b/tools/perf/arch/arm/util/unwind-libunwind.c @@ -3,6 +3,7 @@ #include <libunwind.h> #include "perf_regs.h" #include "../../util/unwind.h" +#include "../../util/debug.h" int libunwind__arch_reg_id(int regnum) { diff --git a/tools/perf/arch/arm64/include/perf_regs.h b/tools/perf/arch/arm64/include/perf_regs.h index e9441b9e2a30..1d3f39c3aa56 100644 --- a/tools/perf/arch/arm64/include/perf_regs.h +++ b/tools/perf/arch/arm64/include/perf_regs.h @@ -6,6 +6,8 @@ #include <asm/perf_regs.h> #define PERF_REGS_MASK ((1ULL << PERF_REG_ARM64_MAX) - 1) +#define PERF_REGS_MAX PERF_REG_ARM64_MAX + #define PERF_REG_IP PERF_REG_ARM64_PC #define PERF_REG_SP PERF_REG_ARM64_SP diff --git a/tools/perf/arch/arm64/util/unwind-libunwind.c b/tools/perf/arch/arm64/util/unwind-libunwind.c index 436ee43859dc..a87afa91a99e 100644 --- a/tools/perf/arch/arm64/util/unwind-libunwind.c +++ b/tools/perf/arch/arm64/util/unwind-libunwind.c @@ -3,6 +3,7 @@ #include <libunwind.h> #include "perf_regs.h" #include "../../util/unwind.h" +#include "../../util/debug.h" int libunwind__arch_reg_id(int regnum) { diff --git a/tools/perf/arch/common.c b/tools/perf/arch/common.c index 42faf369211c..49776f190abf 100644 --- a/tools/perf/arch/common.c +++ b/tools/perf/arch/common.c @@ -12,6 +12,11 @@ const char *const arm_triplets[] = { NULL }; +const char *const arm64_triplets[] = { + "aarch64-linux-android-", + NULL +}; + const char *const powerpc_triplets[] = { "powerpc-unknown-linux-gnu-", "powerpc64-unknown-linux-gnu-", @@ -105,6 +110,8 @@ static const char *normalize_arch(char *arch) return "x86"; if (!strcmp(arch, "sun4u") || !strncmp(arch, "sparc", 5)) return "sparc"; + if (!strcmp(arch, "aarch64") || !strcmp(arch, "arm64")) + return "arm64"; if (!strncmp(arch, "arm", 3) || !strcmp(arch, "sa110")) return "arm"; if (!strncmp(arch, "s390", 4)) @@ -159,6 +166,8 @@ static int perf_session_env__lookup_binutils_path(struct perf_session_env *env, if (!strcmp(arch, "arm")) path_list = arm_triplets; + else if (!strcmp(arch, "arm64")) + path_list = arm64_triplets; else if (!strcmp(arch, "powerpc")) path_list = powerpc_triplets; else if (!strcmp(arch, "sh")) diff --git a/tools/perf/arch/powerpc/Makefile b/tools/perf/arch/powerpc/Makefile index 744e629797be..6f7782bea5dd 100644 --- a/tools/perf/arch/powerpc/Makefile +++ b/tools/perf/arch/powerpc/Makefile @@ -1,5 +1,6 @@ ifndef NO_DWARF PERF_HAVE_DWARF_REGS := 1 LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/skip-callchain-idx.o endif LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o diff --git a/tools/perf/arch/powerpc/util/header.c b/tools/perf/arch/powerpc/util/header.c 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..d73ef8bb08c7 --- /dev/null +++ b/tools/perf/arch/powerpc/util/skip-callchain-idx.c @@ -0,0 +1,267 @@ +/* + * 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" +#include "util/debug.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/tests/dwarf-unwind.c b/tools/perf/arch/x86/tests/dwarf-unwind.c index 9f89f899ccc7..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 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 40021fa3129b..fd2868490d00 100644 --- a/tools/perf/arch/x86/util/tsc.c +++ b/tools/perf/arch/x86/util/tsc.c @@ -6,29 +6,9 @@ #include "../../perf.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 2affe0366b59..2edc4d31065c 100644 --- a/tools/perf/arch/x86/util/tsc.h +++ b/tools/perf/arch/x86/util/tsc.h @@ -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-hash.c b/tools/perf/bench/futex-hash.c index a84206e9c4aa..fc9bebd2cca0 100644 --- a/tools/perf/bench/futex-hash.c +++ b/tools/perf/bench/futex-hash.c @@ -26,6 +26,7 @@ static unsigned int nsecs = 10; /* amount of futexes per thread */ static unsigned int nfutexes = 1024; static bool fshared = false, done = false, silent = false; +static int futex_flag = 0; struct timeval start, end, runtime; static pthread_mutex_t thread_lock; @@ -75,8 +76,7 @@ static void *workerfn(void *arg) * such as internal waitqueue handling, thus enlarging * the critical region protected by hb->lock. */ - ret = futex_wait(&w->futex[i], 1234, NULL, - fshared ? 0 : FUTEX_PRIVATE_FLAG); + ret = futex_wait(&w->futex[i], 1234, NULL, futex_flag); if (!silent && (!ret || errno != EAGAIN || errno != EWOULDBLOCK)) warn("Non-expected futex return call"); @@ -135,6 +135,9 @@ int bench_futex_hash(int argc, const char **argv, if (!worker) goto errmem; + if (!fshared) + futex_flag = FUTEX_PRIVATE_FLAG; + printf("Run summary [PID %d]: %d threads, each operating on %d [%s] futexes for %d secs.\n\n", getpid(), nthreads, nfutexes, fshared ? "shared":"private", nsecs); diff --git a/tools/perf/bench/futex-requeue.c b/tools/perf/bench/futex-requeue.c index a16255876f1d..bedff6b5b3cf 100644 --- a/tools/perf/bench/futex-requeue.c +++ b/tools/perf/bench/futex-requeue.c @@ -29,25 +29,19 @@ 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 bool done = false, silent = false, fshared = false; static pthread_mutex_t thread_lock; static pthread_cond_t thread_parent, thread_worker; static struct stats requeuetime_stats, requeued_stats; static unsigned int ncpus, threads_starting, nthreads = 0; +static int futex_flag = 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_BOOLEAN( 'S', "shared", &fshared, "Use shared futexes instead of private ones"), OPT_END() }; @@ -78,7 +72,7 @@ static void *workerfn(void *arg __maybe_unused) pthread_cond_wait(&thread_worker, &thread_lock); pthread_mutex_unlock(&thread_lock); - futex_wait(&futex1, 0, NULL, FUTEX_PRIVATE_FLAG); + futex_wait(&futex1, 0, NULL, futex_flag); return NULL; } @@ -135,9 +129,12 @@ int bench_futex_requeue(int argc, const char **argv, if (!worker) err(EXIT_FAILURE, "calloc"); - printf("Run summary [PID %d]: Requeuing %d threads (from %p to %p), " - "%d at a time.\n\n", - getpid(), nthreads, &futex1, &futex2, nrequeue); + if (!fshared) + futex_flag = FUTEX_PRIVATE_FLAG; + + printf("Run summary [PID %d]: Requeuing %d threads (from [%s] %p to %p), " + "%d at a time.\n\n", getpid(), nthreads, + fshared ? "shared":"private", &futex1, &futex2, nrequeue); init_stats(&requeued_stats); init_stats(&requeuetime_stats); @@ -146,7 +143,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; @@ -164,16 +161,20 @@ int bench_futex_requeue(int argc, const char **argv, /* Ok, all threads are patiently blocked, start requeueing */ gettimeofday(&start, NULL); - for (nrequeued = 0; nrequeued < nthreads; nrequeued += nrequeue) + for (nrequeued = 0; nrequeued < nthreads; nrequeued += nrequeue) { /* * Do not wakeup any tasks blocked on futex1, allowing * us to really measure futex_wait functionality. */ - futex_cmp_requeue(&futex1, 0, &futex2, 0, nrequeue, - FUTEX_PRIVATE_FLAG); + futex_cmp_requeue(&futex1, 0, &futex2, 0, + nrequeue, futex_flag); + } gettimeofday(&end, NULL); timersub(&end, &start, &runtime); + if (nrequeued > nthreads) + nrequeued = nthreads; + update_stats(&requeued_stats, nrequeued); update_stats(&requeuetime_stats, runtime.tv_usec); @@ -183,7 +184,7 @@ int bench_futex_requeue(int argc, const char **argv, } /* everybody should be blocked on futex2, wake'em up */ - nrequeued = futex_wake(&futex2, nthreads, FUTEX_PRIVATE_FLAG); + nrequeued = futex_wake(&futex2, nthreads, futex_flag); if (nthreads != nrequeued) warnx("couldn't wakeup all tasks (%d/%d)", nrequeued, nthreads); @@ -192,7 +193,6 @@ int bench_futex_requeue(int argc, const char **argv, if (ret) err(EXIT_FAILURE, "pthread_join"); } - } /* cleanup & report results */ diff --git a/tools/perf/bench/futex-wake.c b/tools/perf/bench/futex-wake.c index d096169b161e..929f762be47e 100644 --- a/tools/perf/bench/futex-wake.c +++ b/tools/perf/bench/futex-wake.c @@ -30,25 +30,19 @@ 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, fshared = false; static pthread_mutex_t thread_lock; static pthread_cond_t thread_parent, thread_worker; static struct stats waketime_stats, wakeup_stats; static unsigned int ncpus, threads_starting, nthreads = 0; +static int futex_flag = 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_BOOLEAN( 'S', "shared", &fshared, "Use shared futexes instead of private ones"), OPT_END() }; @@ -66,7 +60,7 @@ static void *workerfn(void *arg __maybe_unused) pthread_cond_wait(&thread_worker, &thread_lock); pthread_mutex_unlock(&thread_lock); - futex_wait(&futex1, 0, NULL, FUTEX_PRIVATE_FLAG); + futex_wait(&futex1, 0, NULL, futex_flag); return NULL; } @@ -138,9 +132,12 @@ int bench_futex_wake(int argc, const char **argv, if (!worker) err(EXIT_FAILURE, "calloc"); - printf("Run summary [PID %d]: blocking on %d threads (at futex %p), " + if (!fshared) + futex_flag = FUTEX_PRIVATE_FLAG; + + printf("Run summary [PID %d]: blocking on %d threads (at [%s] futex %p), " "waking up %d at a time.\n\n", - getpid(), nthreads, &futex1, nwakes); + getpid(), nthreads, fshared ? "shared":"private", &futex1, nwakes); init_stats(&wakeup_stats); init_stats(&waketime_stats); @@ -149,7 +146,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; @@ -168,7 +165,7 @@ int bench_futex_wake(int argc, const char **argv, /* Ok, all threads are patiently blocked, start waking folks up */ gettimeofday(&start, NULL); while (nwoken != nthreads) - nwoken += futex_wake(&futex1, nwakes, FUTEX_PRIVATE_FLAG); + nwoken += futex_wake(&futex1, nwakes, futex_flag); gettimeofday(&end, NULL); timersub(&end, &start, &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..d7f281c2828d 100644 --- a/tools/perf/bench/sched-messaging.c +++ b/tools/perf/bench/sched-messaging.c @@ -26,8 +26,9 @@ #include <sys/socket.h> #include <sys/wait.h> #include <sys/time.h> -#include <sys/poll.h> +#include <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 1ec429fef2be..be5939418425 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -36,7 +36,8 @@ struct perf_annotate { struct perf_tool tool; - bool force, use_tui, use_stdio, use_gtk; + struct perf_session *session; + bool use_tui, use_stdio, use_gtk; bool full_paths; bool print_line; bool skip_missing; @@ -188,18 +189,9 @@ find_next: static int __cmd_annotate(struct perf_annotate *ann) { int ret; - struct perf_session *session; + struct perf_session *session = ann->session; struct perf_evsel *pos; u64 total_nr_samples; - struct perf_data_file file = { - .path = input_name, - .mode = PERF_DATA_MODE_READ, - .force = ann->force, - }; - - session = perf_session__new(&file, false, &ann->tool); - if (session == NULL) - return -ENOMEM; machines__set_symbol_filter(&session->machines, symbol__annotate_init); @@ -207,22 +199,22 @@ static int __cmd_annotate(struct perf_annotate *ann) ret = perf_session__cpu_bitmap(session, ann->cpu_list, ann->cpu_bitmap); if (ret) - goto out_delete; + goto out; } if (!objdump_path) { ret = perf_session_env__lookup_objdump(&session->header.env); if (ret) - goto out_delete; + goto out; } ret = perf_session__process_events(session, &ann->tool); if (ret) - goto out_delete; + goto out; if (dump_trace) { perf_session__fprintf_nr_events(session, stdout); - goto out_delete; + goto out; } if (verbose > 3) @@ -250,8 +242,8 @@ static int __cmd_annotate(struct perf_annotate *ann) } if (total_nr_samples == 0) { - ui__error("The %s file has no samples!\n", file.path); - goto out_delete; + ui__error("The %s file has no samples!\n", session->file->path); + goto out; } if (use_browser == 2) { @@ -261,24 +253,12 @@ static int __cmd_annotate(struct perf_annotate *ann) "perf_gtk__show_annotations"); if (show_annotations == NULL) { ui__error("GTK browser not found!\n"); - goto out_delete; + goto out; } show_annotations(); } -out_delete: - /* - * Speed up the exit process, for large files this can - * take quite a while. - * - * XXX Enable this when using valgrind or if we ever - * librarize this command. - * - * Also experiment with obstacks to see how much speed - * up we'll get here. - * - * perf_session__delete(session); - */ +out: return ret; } @@ -297,10 +277,14 @@ int cmd_annotate(int argc, const char **argv, const char *prefix __maybe_unused) .comm = perf_event__process_comm, .exit = perf_event__process_exit, .fork = perf_event__process_fork, - .ordered_samples = true, + .ordered_events = true, .ordering_requires_timestamps = true, }, }; + struct perf_data_file file = { + .path = input_name, + .mode = PERF_DATA_MODE_READ, + }; const struct option options[] = { OPT_STRING('i', "input", &input_name, "file", "input file name"), @@ -308,7 +292,7 @@ int cmd_annotate(int argc, const char **argv, const char *prefix __maybe_unused) "only consider symbols in these dsos"), OPT_STRING('s', "symbol", &annotate.sym_hist_filter, "symbol", "symbol to annotate"), - OPT_BOOLEAN('f', "force", &annotate.force, "don't complain, do it"), + OPT_BOOLEAN('f', "force", &file.force, "don't complain, do it"), OPT_INCR('v', "verbose", &verbose, "be more verbose (show symbol address, etc)"), OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, @@ -341,6 +325,7 @@ int cmd_annotate(int argc, const char **argv, const char *prefix __maybe_unused) "Show event group information together"), OPT_END() }; + int ret; argc = parse_options(argc, argv, options, annotate_usage, 0); @@ -353,11 +338,16 @@ int cmd_annotate(int argc, const char **argv, const char *prefix __maybe_unused) setup_browser(true); + annotate.session = perf_session__new(&file, false, &annotate.tool); + if (annotate.session == NULL) + return -1; + symbol_conf.priv_size = sizeof(struct annotation); symbol_conf.try_vmlinux_path = true; - if (symbol__init() < 0) - return -1; + ret = symbol__init(&annotate.session->header.env); + if (ret < 0) + goto out_delete; if (setup_sorting() < 0) usage_with_options(annotate_usage, options); @@ -373,5 +363,20 @@ int cmd_annotate(int argc, const char **argv, const char *prefix __maybe_unused) annotate.sym_hist_filter = argv[0]; } - return __cmd_annotate(&annotate); + ret = __cmd_annotate(&annotate); + +out_delete: + /* + * Speed up the exit process, for large files this can + * take quite a while. + * + * XXX Enable this when using valgrind or if we ever + * librarize this command. + * + * Also experiment with obstacks to see how much speed + * up we'll get here. + * + * perf_session__delete(session); + */ + return ret; } 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..70385756da63 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; } @@ -244,20 +246,9 @@ static bool dso__missing_buildid_cache(struct dso *dso, int parm __maybe_unused) return true; } -static int build_id_cache__fprintf_missing(const char *filename, bool force, FILE *fp) +static int build_id_cache__fprintf_missing(struct perf_session *session, FILE *fp) { - struct perf_data_file file = { - .path = filename, - .mode = PERF_DATA_MODE_READ, - .force = force, - }; - struct perf_session *session = perf_session__new(&file, false, NULL); - if (session == NULL) - return -1; - perf_session__fprintf_dsos_buildid(session, fp, dso__missing_buildid_cache, 0); - perf_session__delete(session); - return 0; } @@ -300,6 +291,12 @@ int cmd_buildid_cache(int argc, const char **argv, *missing_filename = NULL, *update_name_list_str = NULL, *kcore_filename; + char sbuf[STRERR_BUFSIZE]; + + struct perf_data_file file = { + .mode = PERF_DATA_MODE_READ, + }; + struct perf_session *session = NULL; const struct option buildid_cache_options[] = { OPT_STRING('a', "add", &add_name_list_str, @@ -324,8 +321,17 @@ int cmd_buildid_cache(int argc, const char **argv, argc = parse_options(argc, argv, buildid_cache_options, buildid_cache_usage, 0); - if (symbol__init() < 0) - return -1; + if (missing_filename) { + file.path = missing_filename; + file.force = force; + + session = perf_session__new(&file, false, NULL); + if (session == NULL) + return -1; + } + + if (symbol__init(session ? &session->header.env : NULL) < 0) + goto out; setup_pager(); @@ -342,7 +348,7 @@ int cmd_buildid_cache(int argc, const char **argv, continue; } pr_warning("Couldn't add %s: %s\n", - pos->s, strerror(errno)); + pos->s, strerror_r(errno, sbuf, sizeof(sbuf))); } strlist__delete(list); @@ -360,7 +366,7 @@ int cmd_buildid_cache(int argc, const char **argv, continue; } pr_warning("Couldn't remove %s: %s\n", - pos->s, strerror(errno)); + pos->s, strerror_r(errno, sbuf, sizeof(sbuf))); } strlist__delete(list); @@ -368,7 +374,7 @@ int cmd_buildid_cache(int argc, const char **argv, } if (missing_filename) - ret = build_id_cache__fprintf_missing(missing_filename, force, stdout); + ret = build_id_cache__fprintf_missing(session, stdout); if (update_name_list_str) { list = strlist__new(true, update_name_list_str); @@ -381,7 +387,7 @@ int cmd_buildid_cache(int argc, const char **argv, continue; } pr_warning("Couldn't update %s: %s\n", - pos->s, strerror(errno)); + pos->s, strerror_r(errno, sbuf, sizeof(sbuf))); } strlist__delete(list); @@ -389,8 +395,12 @@ 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); +out: + if (session) + perf_session__delete(session); + return ret; } diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index 9a5a035cb426..a3ce19f7aebd 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -360,7 +360,7 @@ static struct perf_tool tool = { .exit = perf_event__process_exit, .fork = perf_event__process_fork, .lost = perf_event__process_lost, - .ordered_samples = true, + .ordered_events = true, .ordering_requires_timestamps = true, }; @@ -683,7 +683,7 @@ static int __cmd_diff(void) d->session = perf_session__new(&d->file, false, &tool); if (!d->session) { pr_err("Failed to open %s\n", d->file.path); - ret = -ENOMEM; + ret = -1; goto out_delete; } @@ -1143,7 +1143,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) argc = parse_options(argc, argv, options, diff_usage, 0); - if (symbol__init() < 0) + if (symbol__init(NULL) < 0) return -1; if (data_init(argc, argv) < 0) diff --git a/tools/perf/builtin-evlist.c b/tools/perf/builtin-evlist.c index c99e0de7e54a..0f93f859b782 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) { @@ -27,7 +28,7 @@ static int __cmd_evlist(const char *file_name, struct perf_attr_details *details session = perf_session__new(&file, 0, NULL); if (session == NULL) - return -ENOMEM; + return -1; evlist__for_each(session->evlist, pos) perf_evsel__fprintf(pos, details, stdout); diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c index 178b88ae3d2f..25d20628212e 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; @@ -102,6 +103,8 @@ static int check_emacsclient_version(void) static void exec_woman_emacs(const char *path, const char *page) { + char sbuf[STRERR_BUFSIZE]; + if (!check_emacsclient_version()) { /* This works only with emacsclient version >= 22. */ struct strbuf man_page = STRBUF_INIT; @@ -110,16 +113,19 @@ static void exec_woman_emacs(const char *path, const char *page) path = "emacsclient"; strbuf_addf(&man_page, "(woman \"%s\")", page); execlp(path, "emacsclient", "-e", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); + warning("failed to exec '%s': %s", path, + strerror_r(errno, sbuf, sizeof(sbuf))); } } static void exec_man_konqueror(const char *path, const char *page) { const char *display = getenv("DISPLAY"); + if (display && *display) { struct strbuf man_page = STRBUF_INIT; const char *filename = "kfmclient"; + char sbuf[STRERR_BUFSIZE]; /* It's simpler to launch konqueror using kfmclient. */ if (path) { @@ -138,24 +144,31 @@ static void exec_man_konqueror(const char *path, const char *page) path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); execlp(path, filename, "newTab", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); + warning("failed to exec '%s': %s", path, + strerror_r(errno, sbuf, sizeof(sbuf))); } } static void exec_man_man(const char *path, const char *page) { + char sbuf[STRERR_BUFSIZE]; + if (!path) path = "man"; execlp(path, "man", page, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); + warning("failed to exec '%s': %s", path, + strerror_r(errno, sbuf, sizeof(sbuf))); } static void exec_man_cmd(const char *cmd, const char *page) { struct strbuf shell_cmd = STRBUF_INIT; + char sbuf[STRERR_BUFSIZE]; + strbuf_addf(&shell_cmd, "%s %s", cmd, page); execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); - warning("failed to exec '%s': %s", cmd, strerror(errno)); + warning("failed to exec '%s': %s", cmd, + strerror_r(errno, sbuf, sizeof(sbuf))); } static void add_man_viewer(const char *name) diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 16c7c11ad06e..de99ca1bb942 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -23,6 +23,7 @@ struct perf_inject { struct perf_tool tool; + struct perf_session *session; bool build_ids; bool sched_stat; const char *input_name; @@ -340,12 +341,8 @@ static int perf_evsel__check_stype(struct perf_evsel *evsel, static int __cmd_inject(struct perf_inject *inject) { - struct perf_session *session; int ret = -EINVAL; - struct perf_data_file file = { - .path = inject->input_name, - .mode = PERF_DATA_MODE_READ, - }; + struct perf_session *session = inject->session; struct perf_data_file *file_out = &inject->output; signal(SIGINT, sig_handler); @@ -357,16 +354,12 @@ static int __cmd_inject(struct perf_inject *inject) inject->tool.tracing_data = perf_event__repipe_tracing_data; } - session = perf_session__new(&file, true, &inject->tool); - if (session == NULL) - return -ENOMEM; - if (inject->build_ids) { inject->tool.sample = perf_event__inject_buildid; } else if (inject->sched_stat) { struct perf_evsel *evsel; - inject->tool.ordered_samples = true; + inject->tool.ordered_events = true; evlist__for_each(session->evlist, evsel) { const char *name = perf_evsel__name(evsel); @@ -389,12 +382,13 @@ 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); } - perf_session__delete(session); - return ret; } @@ -424,6 +418,11 @@ int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused) .mode = PERF_DATA_MODE_WRITE, }, }; + struct perf_data_file file = { + .mode = PERF_DATA_MODE_READ, + }; + int ret; + const struct option options[] = { OPT_BOOLEAN('b', "build-ids", &inject.build_ids, "Inject build-ids into the output stream"), @@ -436,6 +435,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[] = { @@ -456,8 +457,17 @@ int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused) return -1; } - if (symbol__init() < 0) + file.path = inject.input_name; + inject.session = perf_session__new(&file, true, &inject.tool); + if (inject.session == NULL) + return -1; + + if (symbol__init(&inject.session->header.env) < 0) return -1; - return __cmd_inject(&inject); + ret = __cmd_inject(&inject); + + perf_session__delete(inject.session); + + return ret; } diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index bef3376bfaf3..f295141025bc 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -256,7 +256,9 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, static struct perf_tool perf_kmem = { .sample = process_sample_event, .comm = perf_event__process_comm, - .ordered_samples = true, + .mmap = perf_event__process_mmap, + .mmap2 = perf_event__process_mmap2, + .ordered_events = true, }; static double fragmentation(unsigned long n_req, unsigned long n_alloc) @@ -403,10 +405,9 @@ static void sort_result(void) __sort_result(&root_caller_stat, &root_caller_sorted, &caller_sort); } -static int __cmd_kmem(void) +static int __cmd_kmem(struct perf_session *session) { int err = -EINVAL; - struct perf_session *session; const struct perf_evsel_str_handler kmem_tracepoints[] = { { "kmem:kmalloc", perf_evsel__process_alloc_event, }, { "kmem:kmem_cache_alloc", perf_evsel__process_alloc_event, }, @@ -415,34 +416,22 @@ static int __cmd_kmem(void) { "kmem:kfree", perf_evsel__process_free_event, }, { "kmem:kmem_cache_free", perf_evsel__process_free_event, }, }; - struct perf_data_file file = { - .path = input_name, - .mode = PERF_DATA_MODE_READ, - }; - - session = perf_session__new(&file, false, &perf_kmem); - if (session == NULL) - return -ENOMEM; - - if (perf_session__create_kernel_maps(session) < 0) - goto out_delete; if (!perf_session__has_traces(session, "kmem record")) - goto out_delete; + goto out; if (perf_session__set_tracepoints_handlers(session, kmem_tracepoints)) { pr_err("Initializing perf session tracepoint handlers failed\n"); - return -1; + goto out; } setup_pager(); err = perf_session__process_events(session, &perf_kmem); if (err != 0) - goto out_delete; + goto out; sort_result(); print_result(session); -out_delete: - perf_session__delete(session); +out: return err; } @@ -689,29 +678,46 @@ int cmd_kmem(int argc, const char **argv, const char *prefix __maybe_unused) NULL, NULL }; + struct perf_session *session; + struct perf_data_file file = { + .path = input_name, + .mode = PERF_DATA_MODE_READ, + }; + int ret = -1; + argc = parse_options_subcommand(argc, argv, kmem_options, kmem_subcommands, kmem_usage, 0); if (!argc) usage_with_options(kmem_usage, kmem_options); - symbol__init(); - if (!strncmp(argv[0], "rec", 3)) { + symbol__init(NULL); return __cmd_record(argc, argv); - } else if (!strcmp(argv[0], "stat")) { + } + + session = perf_session__new(&file, false, &perf_kmem); + if (session == NULL) + return -1; + + symbol__init(&session->header.env); + + if (!strcmp(argv[0], "stat")) { if (cpu__setup_cpunode_map()) - return -1; + goto out_delete; if (list_empty(&caller_sort)) setup_sorting(&caller_sort, default_sort_order); if (list_empty(&alloc_sort)) setup_sorting(&alloc_sort, default_sort_order); - return __cmd_kmem(); + ret = __cmd_kmem(session); } else usage_with_options(kmem_usage, kmem_options); - return 0; +out_delete: + perf_session__delete(session); + + return ret; } diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c index 0f1e5a2f6ad7..d8bf2271f4ea 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; -}; - -#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; +#ifdef HAVE_KVM_STAT_SUPPORT +#include <asm/kvm_perf.h> +#include "util/kvm-stat.h" - 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]) -{ - const char *exit_reason = get_exit_reason(kvm, key->key); - - scnprintf(decode, 20, "%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) +void exit_event_decode_key(struct perf_kvm_stat *kvm, + struct event_key *key, + char *decode) { - /* 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; - } + const char *exit_reason = get_exit_reason(kvm, key->exit_reasons, + key->key); - 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"); + scnprintf(decode, DECODE_STR_LEN, "%s", exit_reason); } -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); @@ -707,14 +543,12 @@ static void print_vcpu_info(struct perf_kvm_stat *kvm) pr_info("Analyze events for "); - if (kvm->live) { - if (kvm->opts.target.system_wide) - pr_info("all VMs, "); - else if (kvm->opts.target.pid) - pr_info("pid(s) %s, ", kvm->opts.target.pid); - else - pr_info("dazed and confused on what is monitored, "); - } + if (kvm->opts.target.system_wide) + pr_info("all VMs, "); + else if (kvm->opts.target.pid) + pr_info("pid(s) %s, ", kvm->opts.target.pid); + else + pr_info("dazed and confused on what is monitored, "); if (vcpu == -1) pr_info("all VCPUs:\n\n"); @@ -740,7 +574,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,13 +585,13 @@ 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%"); pr_info("%9s ", "Time%"); - pr_info("%10s ", "Min Time"); - pr_info("%10s ", "Max Time"); + pr_info("%11s ", "Min Time"); + pr_info("%11s ", "Max Time"); pr_info("%16s ", "Avg time"); pr_info("\n\n"); @@ -770,12 +604,12 @@ 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); - pr_info("%8" PRIu64 "us ", min / 1000); - pr_info("%8" PRIu64 "us ", max / 1000); + pr_info("%9.2fus ", (double)min / 1e3); + pr_info("%9.2fus ", (double)max / 1e3); pr_info("%9.2fus ( +-%7.2f%% )", (double)etime / ecount/1e3, kvm_event_rel_stddev(vcpu, event)); pr_info("\n"); @@ -839,34 +673,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) @@ -902,7 +730,7 @@ static s64 perf_kvm__mmap_read_idx(struct perf_kvm_stat *kvm, int idx, return -1; } - err = perf_session_queue_event(kvm->session, event, &sample, 0); + err = perf_session_queue_event(kvm->session, event, &kvm->tool, &sample, 0); /* * FIXME: Here we can't consume the event, as perf_session_queue_event will * point to it, and it'll get possibly overwritten by the kernel. @@ -955,7 +783,7 @@ static int perf_kvm__mmap_read(struct perf_kvm_stat *kvm) /* flush queue after each round in which we processed events */ if (ntotal) { - kvm->session->ordered_samples.next_flush = flush_time; + kvm->session->ordered_events.next_flush = flush_time; err = kvm->tool.finished_round(&kvm->tool, NULL, kvm->session); if (err) { if (kvm->lost_events) @@ -1055,15 +883,11 @@ static int fd_set_nonblock(int fd) return 0; } -static -int perf_kvm__handle_stdin(struct termios *tc_now, struct termios *tc_save) +static int perf_kvm__handle_stdin(void) { int c; - tcsetattr(0, TCSANOW, tc_now); c = getc(stdin); - tcsetattr(0, TCSAFLUSH, tc_save); - if (c == 'q') return 1; @@ -1074,7 +898,7 @@ static int kvm_events_live_report(struct perf_kvm_stat *kvm) { struct pollfd *pollfds = NULL; int nr_fds, nr_stdin, ret, err = -EINVAL; - struct termios tc, save; + struct termios save; /* live flag must be set first */ kvm->live = true; @@ -1089,26 +913,14 @@ static int kvm_events_live_report(struct perf_kvm_stat *kvm) goto out; } + set_term_quiet_input(&save); init_kvm_event_record(kvm); - tcgetattr(0, &save); - tc = save; - tc.c_lflag &= ~(ICANON | ECHO); - tc.c_cc[VMIN] = 0; - tc.c_cc[VTIME] = 0; - signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); - /* copy pollfds -- need to add timerfd and stdin */ - nr_fds = kvm->evlist->nr_fds; - pollfds = zalloc(sizeof(struct pollfd) * (nr_fds + 2)); - if (!pollfds) { - err = -ENOMEM; - goto out; - } - memcpy(pollfds, kvm->evlist->pollfd, - sizeof(struct pollfd) * kvm->evlist->nr_fds); + /* use pollfds -- need to add timerfd and stdin */ + nr_fds = kvm->evlist->pollfd.nr; /* add timer fd */ if (perf_kvm__timerfd_create(kvm) < 0) { @@ -1116,17 +928,21 @@ static int kvm_events_live_report(struct perf_kvm_stat *kvm) goto out; } - pollfds[nr_fds].fd = kvm->timerfd; - pollfds[nr_fds].events = POLLIN; + if (perf_evlist__add_pollfd(kvm->evlist, kvm->timerfd)) + goto out; + nr_fds++; - pollfds[nr_fds].fd = fileno(stdin); - pollfds[nr_fds].events = POLLIN; + if (perf_evlist__add_pollfd(kvm->evlist, fileno(stdin))) + goto out; + nr_stdin = nr_fds; nr_fds++; if (fd_set_nonblock(fileno(stdin)) != 0) goto out; + pollfds = kvm->evlist->pollfd.entries; + /* everything is good - enable the events and process */ perf_evlist__enable(kvm->evlist); @@ -1142,7 +958,7 @@ static int kvm_events_live_report(struct perf_kvm_stat *kvm) goto out; if (pollfds[nr_stdin].revents & POLLIN) - done = perf_kvm__handle_stdin(&tc, &save); + done = perf_kvm__handle_stdin(); if (!rc && !done) err = poll(pollfds, nr_fds, 100); @@ -1159,7 +975,7 @@ out: if (kvm->timerfd >= 0) close(kvm->timerfd); - free(pollfds); + tcsetattr(0, TCSAFLUSH, &save); return err; } @@ -1168,6 +984,7 @@ static int kvm_live_open_events(struct perf_kvm_stat *kvm) int err, rc = -1; struct perf_evsel *pos; struct perf_evlist *evlist = kvm->evlist; + char sbuf[STRERR_BUFSIZE]; perf_evlist__config(evlist, &kvm->opts); @@ -1204,12 +1021,14 @@ static int kvm_live_open_events(struct perf_kvm_stat *kvm) err = perf_evlist__open(evlist); if (err < 0) { - printf("Couldn't create the events: %s\n", strerror(errno)); + printf("Couldn't create the events: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out; } if (perf_evlist__mmap(evlist, kvm->opts.mmap_pages, false) < 0) { - ui__error("Failed to mmap the events: %s\n", strerror(errno)); + ui__error("Failed to mmap the events: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); perf_evlist__close(evlist); goto out; } @@ -1228,7 +1047,7 @@ static int read_events(struct perf_kvm_stat *kvm) struct perf_tool eops = { .sample = process_sample_event, .comm = perf_event__process_comm, - .ordered_samples = true, + .ordered_events = true, }; struct perf_data_file file = { .path = kvm->file_name, @@ -1239,9 +1058,11 @@ static int read_events(struct perf_kvm_stat *kvm) kvm->session = perf_session__new(&file, false, &kvm->tool); if (!kvm->session) { pr_err("Initializing perf session failed\n"); - return -EINVAL; + return -1; } + symbol__init(&kvm->session->header.env); + if (!perf_session__has_traces(kvm->session, "kvm record")) return -EINVAL; @@ -1258,8 +1079,8 @@ static int read_events(struct perf_kvm_stat *kvm) static int parse_target_str(struct perf_kvm_stat *kvm) { - if (kvm->pid_str) { - kvm->pid_list = intlist__new(kvm->pid_str); + if (kvm->opts.target.pid) { + kvm->pid_list = intlist__new(kvm->opts.target.pid); if (kvm->pid_list == NULL) { pr_err("Error parsing process id string\n"); return -EINVAL; @@ -1300,13 +1121,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 +1132,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 +1140,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 +1156,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,13 +1175,14 @@ 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", "key for sorting: sample(sort by samples number)" " time (sort by avg time)"), - OPT_STRING('p', "pid", &kvm->pid_str, "pid", + OPT_STRING('p', "pid", &kvm->opts.target.pid, "pid", "analyze events only for given process id(s)"), OPT_END() }; @@ -1372,8 +1192,6 @@ kvm_events_report(struct perf_kvm_stat *kvm, int argc, const char **argv) NULL }; - symbol__init(); - if (argc) { argc = parse_options(argc, argv, kvm_events_report_options, @@ -1383,6 +1201,9 @@ kvm_events_report(struct perf_kvm_stat *kvm, int argc, const char **argv) kvm_events_report_options); } + if (!kvm->opts.target.pid) + kvm->opts.target.system_wide = true; + return kvm_events_report_vcpu(kvm); } @@ -1391,16 +1212,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 +1230,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 +1238,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 +1283,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[] = { @@ -1480,7 +1303,7 @@ static int kvm_events_live(struct perf_kvm_stat *kvm, kvm->tool.exit = perf_event__process_exit; kvm->tool.fork = perf_event__process_fork; kvm->tool.lost = process_lost_event; - kvm->tool.ordered_samples = true; + kvm->tool.ordered_events = true; perf_tool__fill_defaults(&kvm->tool); /* set defaults */ @@ -1491,7 +1314,7 @@ static int kvm_events_live(struct perf_kvm_stat *kvm, kvm->opts.target.uid_str = NULL; kvm->opts.target.uid = UINT_MAX; - symbol__init(); + symbol__init(NULL); disable_buildid_cache(); use_browser = 0; @@ -1538,7 +1361,7 @@ static int kvm_events_live(struct perf_kvm_stat *kvm, */ kvm->session = perf_session__new(&file, false, &kvm->tool); if (kvm->session == NULL) { - err = -ENOMEM; + err = -1; goto out; } kvm->session->evlist = kvm->evlist; @@ -1585,9 +1408,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 +1429,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 +1546,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 6148afc995c6..e7ec71589da6 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -852,7 +852,7 @@ static int __cmd_report(bool display_info) struct perf_tool eops = { .sample = process_sample_event, .comm = perf_event__process_comm, - .ordered_samples = true, + .ordered_events = true, }; struct perf_data_file file = { .path = input_name, @@ -862,9 +862,11 @@ static int __cmd_report(bool display_info) session = perf_session__new(&file, false, &eops); if (!session) { pr_err("Initializing perf session failed\n"); - return -ENOMEM; + return -1; } + symbol__init(&session->header.env); + if (!perf_session__has_traces(session, "lock record")) goto out_delete; @@ -974,7 +976,6 @@ int cmd_lock(int argc, const char **argv, const char *prefix __maybe_unused) unsigned int i; int rc = 0; - symbol__init(); for (i = 0; i < LOCKHASH_SIZE; i++) INIT_LIST_HEAD(lockhash_table + i); diff --git a/tools/perf/builtin-mem.c b/tools/perf/builtin-mem.c index 4a1a6c94a5eb..24db6ffe2957 100644 --- a/tools/perf/builtin-mem.c +++ b/tools/perf/builtin-mem.c @@ -124,7 +124,7 @@ static int report_raw_events(struct perf_mem *mem) &mem->tool); if (session == NULL) - return -ENOMEM; + return -1; if (mem->cpu_list) { ret = perf_session__cpu_bitmap(session, mem->cpu_list, @@ -133,7 +133,7 @@ static int report_raw_events(struct perf_mem *mem) goto out_delete; } - if (symbol__init() < 0) + if (symbol__init(&session->header.env) < 0) return -1; printf("# PID, TID, IP, ADDR, LOCAL WEIGHT, DSRC, SYMBOL\n"); @@ -194,7 +194,7 @@ int cmd_mem(int argc, const char **argv, const char *prefix __maybe_unused) .lost = perf_event__process_lost, .fork = perf_event__process_fork, .build_id = perf_event__process_build_id, - .ordered_samples = true, + .ordered_events = true, }, .input_name = "perf.data", }; diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index c63fa2925075..04412b4770a2 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -290,8 +290,11 @@ static void cleanup_params(void) static void pr_err_with_code(const char *msg, int err) { + char sbuf[STRERR_BUFSIZE]; + pr_err("%s", msg); - pr_debug(" Reason: %s (Code: %d)", strerror(-err), err); + pr_debug(" Reason: %s (Code: %d)", + strerror_r(-err, sbuf, sizeof(sbuf)), err); pr_err("\n"); } @@ -373,6 +376,8 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) "target executable name or path", opt_set_target), OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle, "Disable symbol demangling"), + OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel, + "Enable kernel symbol demangling"), OPT_END() }; int ret; @@ -467,7 +472,8 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) usage_with_options(probe_usage, options); } - ret = show_line_range(¶ms.line_range, params.target); + ret = show_line_range(¶ms.line_range, params.target, + params.uprobes); if (ret < 0) pr_err_with_code(" Error: Failed to show lines.", ret); return ret; diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 378b85b731a7..44c6f3d55ce7 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -65,8 +65,9 @@ static int process_synthesized_event(struct perf_tool *tool, return record__write(rec, event, event->header.size); } -static int record__mmap_read(struct record *rec, struct perf_mmap *md) +static int record__mmap_read(struct record *rec, int idx) { + struct perf_mmap *md = &rec->evlist->mmap[idx]; unsigned int head = perf_mmap__read_head(md); unsigned int old = md->prev; unsigned char *data = md->base + page_size; @@ -102,8 +103,7 @@ static int record__mmap_read(struct record *rec, struct perf_mmap *md) } md->prev = old; - perf_mmap__write_tail(md, old); - + perf_evlist__mmap_consume(rec->evlist, idx); out: return rc; } @@ -161,7 +161,7 @@ try_again: if (perf_evlist__apply_filters(evlist)) { error("failed to set filter with %d (%s)\n", errno, - strerror(errno)); + strerror_r(errno, msg, sizeof(msg))); rc = -1; goto out; } @@ -175,7 +175,8 @@ try_again: "(current value: %u)\n", opts->mmap_pages); rc = -errno; } else { - pr_err("failed to mmap with %d (%s)\n", errno, strerror(errno)); + pr_err("failed to mmap with %d (%s)\n", errno, + strerror_r(errno, msg, sizeof(msg))); rc = -errno; } goto out; @@ -238,19 +239,24 @@ 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; for (i = 0; i < rec->evlist->nr_mmaps; i++) { if (rec->evlist->mmap[i].base) { - if (record__mmap_read(rec, &rec->evlist->mmap[i]) != 0) { + if (record__mmap_read(rec, i) != 0) { rc = -1; goto out; } } } - 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: @@ -302,7 +308,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) struct record_opts *opts = &rec->opts; struct perf_data_file *file = &rec->file; struct perf_session *session; - bool disabled = false; + bool disabled = false, draining = false; rec->progname = argv[0]; @@ -451,9 +457,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) } if (hits == rec->samples) { - if (done) + if (done || draining) break; - err = poll(rec->evlist->pollfd, rec->evlist->nr_fds, -1); + err = perf_evlist__poll(rec->evlist, -1); /* * Propagate error, only if there's any. Ignore positive * number of returned events and interrupt error. @@ -461,6 +467,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) if (err > 0 || (err < 0 && errno == EINTR)) err = 0; waking++; + + if (perf_evlist__filter_pollfd(rec->evlist, POLLERR | POLLHUP) == 0) + draining = true; } /* @@ -475,7 +484,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) } if (forks && workload_exec_errno) { - char msg[512]; + char msg[STRERR_BUFSIZE]; const char *emsg = strerror_r(workload_exec_errno, msg, sizeof(msg)); pr_err("Workload failed: %s\n", emsg); err = -1; @@ -615,145 +624,56 @@ error: return ret; } -#ifdef HAVE_DWARF_UNWIND_SUPPORT -static int get_stack_size(char *str, unsigned long *_size) -{ - char *endptr; - unsigned long size; - unsigned long max_size = round_down(USHRT_MAX, sizeof(u64)); - - size = strtoul(str, &endptr, 0); - - do { - if (*endptr) - break; - - size = round_up(size, sizeof(u64)); - if (!size || size > max_size) - break; - - *_size = size; - return 0; - - } while (0); - - pr_err("callchain: Incorrect stack dump size (max %ld): %s\n", - max_size, str); - return -1; -} -#endif /* HAVE_DWARF_UNWIND_SUPPORT */ - -int record_parse_callchain(const char *arg, struct record_opts *opts) -{ - char *tok, *name, *saveptr = NULL; - char *buf; - int ret = -1; - - /* We need buffer that we know we can write to. */ - buf = malloc(strlen(arg) + 1); - if (!buf) - return -ENOMEM; - - strcpy(buf, arg); - - tok = strtok_r((char *)buf, ",", &saveptr); - name = tok ? : (char *)buf; - - do { - /* Framepointer style */ - if (!strncmp(name, "fp", sizeof("fp"))) { - if (!strtok_r(NULL, ",", &saveptr)) { - opts->call_graph = CALLCHAIN_FP; - ret = 0; - } else - pr_err("callchain: No more arguments " - "needed for -g fp\n"); - break; - -#ifdef HAVE_DWARF_UNWIND_SUPPORT - /* Dwarf style */ - } else if (!strncmp(name, "dwarf", sizeof("dwarf"))) { - const unsigned long default_stack_dump_size = 8192; - - ret = 0; - opts->call_graph = CALLCHAIN_DWARF; - opts->stack_dump_size = default_stack_dump_size; - - tok = strtok_r(NULL, ",", &saveptr); - if (tok) { - unsigned long size = 0; - - ret = get_stack_size(tok, &size); - opts->stack_dump_size = size; - } -#endif /* HAVE_DWARF_UNWIND_SUPPORT */ - } else { - pr_err("callchain: Unknown --call-graph option " - "value: %s\n", arg); - break; - } - - } while (0); - - free(buf); - return ret; -} - -static void callchain_debug(struct record_opts *opts) +static void callchain_debug(void) { static const char *str[CALLCHAIN_MAX] = { "NONE", "FP", "DWARF" }; - pr_debug("callchain: type %s\n", str[opts->call_graph]); + pr_debug("callchain: type %s\n", str[callchain_param.record_mode]); - if (opts->call_graph == CALLCHAIN_DWARF) + if (callchain_param.record_mode == CALLCHAIN_DWARF) pr_debug("callchain: stack dump size %d\n", - opts->stack_dump_size); + callchain_param.dump_size); } -int record_parse_callchain_opt(const struct option *opt, +int record_parse_callchain_opt(const struct option *opt __maybe_unused, const char *arg, int unset) { - struct record_opts *opts = opt->value; int ret; - opts->call_graph_enabled = !unset; + callchain_param.enabled = !unset; /* --no-call-graph */ if (unset) { - opts->call_graph = CALLCHAIN_NONE; + callchain_param.record_mode = CALLCHAIN_NONE; pr_debug("callchain: disabled\n"); return 0; } - ret = record_parse_callchain(arg, opts); + ret = parse_callchain_record_opt(arg); if (!ret) - callchain_debug(opts); + callchain_debug(); return ret; } -int record_callchain_opt(const struct option *opt, +int record_callchain_opt(const struct option *opt __maybe_unused, const char *arg __maybe_unused, int unset __maybe_unused) { - struct record_opts *opts = opt->value; + callchain_param.enabled = true; - opts->call_graph_enabled = !unset; + if (callchain_param.record_mode == CALLCHAIN_NONE) + callchain_param.record_mode = CALLCHAIN_FP; - if (opts->call_graph == CALLCHAIN_NONE) - opts->call_graph = CALLCHAIN_FP; - - callchain_debug(opts); + callchain_debug(); return 0; } static int perf_record_config(const char *var, const char *value, void *cb) { - struct record *rec = cb; - if (!strcmp(var, "record.call-graph")) - return record_parse_callchain(value, &rec->opts); + var = "call-graph.record-mode"; /* fall-through */ return perf_default_config(var, value, cb); } @@ -776,6 +696,7 @@ static const char * const record_usage[] = { */ static struct record record = { .opts = { + .sample_time = true, .mmap_pages = UINT_MAX, .user_freq = UINT_MAX, .user_interval = ULLONG_MAX, @@ -902,7 +823,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused) usage_with_options(record_usage, record_options); } - symbol__init(); + symbol__init(NULL); if (symbol_conf.kptr_restrict) pr_warning( diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 21d830bafff3..ac145fae0521 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -58,17 +58,19 @@ struct report { const char *symbol_filter_str; float min_percent; u64 nr_entries; + u64 queue_size; DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); }; static int report__config(const char *var, const char *value, void *cb) { + struct report *rep = cb; + if (!strcmp(var, "report.group")) { symbol_conf.event_group = perf_config_bool(var, value); return 0; } if (!strcmp(var, "report.percent-limit")) { - struct report *rep = cb; rep->min_percent = strtof(value, NULL); return 0; } @@ -76,6 +78,10 @@ static int report__config(const char *var, const char *value, void *cb) symbol_conf.cumulate_callchain = perf_config_bool(var, value); return 0; } + if (!strcmp(var, "report.queue-size")) { + rep->queue_size = perf_config_u64(var, value); + return 0; + } return perf_default_config(var, value, cb); } @@ -578,7 +584,7 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) .attr = perf_event__process_attr, .tracing_data = perf_event__process_tracing_data, .build_id = perf_event__process_build_id, - .ordered_samples = true, + .ordered_events = true, .ordering_requires_timestamps = true, }, .max_stack = PERF_MAX_STACK_DEPTH, @@ -674,6 +680,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) "objdump binary to use for disassembly and annotations"), OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle, "Disable symbol demangling"), + OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel, + "Enable kernel symbol demangling"), 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), @@ -712,14 +720,19 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) repeat: session = perf_session__new(&file, false, &report.tool); if (session == NULL) - return -ENOMEM; + return -1; + + if (report.queue_size) { + ordered_events__set_alloc_size(&session->ordered_events, + report.queue_size); + } report.session = session; 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) || branch_mode == 1) { sort__mode = SORT_MODE__BRANCH; symbol_conf.cumulate_callchain = false; } @@ -787,7 +800,7 @@ repeat: } } - if (symbol__init() < 0) + if (symbol__init(&session->header.env) < 0) goto error; if (argc) { diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index c38d06c04775..9c9287fbf8e9 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" @@ -427,6 +428,7 @@ static u64 get_cpu_usage_nsec_parent(void) static int self_open_counters(void) { struct perf_event_attr attr; + char sbuf[STRERR_BUFSIZE]; int fd; memset(&attr, 0, sizeof(attr)); @@ -434,11 +436,13 @@ 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 " - "with %d (%s)\n", fd, strerror(errno)); + "with %d (%s)\n", fd, + strerror_r(errno, sbuf, sizeof(sbuf))); return fd; } @@ -935,8 +939,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) { @@ -979,7 +983,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; @@ -1012,7 +1016,7 @@ static int latency_wakeup_event(struct perf_sched *sched, struct thread *wakee; u64 timestamp = sample->time; - 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)) @@ -1072,7 +1076,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)) @@ -1290,7 +1294,7 @@ static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel, return -1; } - 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; @@ -1460,6 +1464,8 @@ static int perf_sched__read_events(struct perf_sched *sched, return -1; } + symbol__init(&session->header.env); + if (perf_session__set_tracepoints_handlers(session, handlers)) goto out_delete; @@ -1660,7 +1666,7 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) .comm = perf_event__process_comm, .lost = perf_event__process_lost, .fork = perf_sched__process_fork_event, - .ordered_samples = true, + .ordered_events = true, }, .cmp_pid = LIST_HEAD_INIT(sched.cmp_pid), .sort_list = LIST_HEAD_INIT(sched.sort_list), @@ -1745,7 +1751,6 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) if (!strcmp(argv[0], "script")) return cmd_script(argc, argv, prefix); - symbol__init(); if (!strncmp(argv[0], "rec", 3)) { return __cmd_record(argc, argv); } else if (!strncmp(argv[0], "lat", 3)) { diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index 9e9c91f5b7fa..b9b9e58a6c39 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -184,10 +184,6 @@ static int perf_evsel__check_attr(struct perf_evsel *evsel, if (perf_evsel__check_stype(evsel, PERF_SAMPLE_IP, "IP", PERF_OUTPUT_IP)) return -EINVAL; - - if (!no_callchain && - !(attr->sample_type & PERF_SAMPLE_CALLCHAIN)) - symbol_conf.use_callchain = false; } if (PRINT_FIELD(ADDR) && @@ -290,6 +286,19 @@ static int perf_session__check_output_opt(struct perf_session *session) set_print_ip_opts(&evsel->attr); } + if (!no_callchain) { + bool use_callchain = false; + + evlist__for_each(session->evlist, evsel) { + if (evsel->attr.sample_type & PERF_SAMPLE_CALLCHAIN) { + use_callchain = true; + break; + } + } + if (!use_callchain) + symbol_conf.use_callchain = false; + } + /* * set default for tracepoints to print symbols only * if callchains are present @@ -358,27 +367,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 +374,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 +404,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"); } @@ -498,6 +485,11 @@ static int default_start_script(const char *script __maybe_unused, return 0; } +static int default_flush_script(void) +{ + return 0; +} + static int default_stop_script(void) { return 0; @@ -511,6 +503,7 @@ static int default_generate_script(struct pevent *pevent __maybe_unused, static struct scripting_ops default_scripting_ops = { .start_script = default_start_script, + .flush_script = default_flush_script, .stop_script = default_stop_script, .process_event = process_event, .generate_script = default_generate_script, @@ -526,6 +519,11 @@ static void setup_scripting(void) scripting_ops = &default_scripting_ops; } +static int flush_scripting(void) +{ + return scripting_ops->flush_script(); +} + static int cleanup_scripting(void) { pr_debug("\nperf script stopped\n"); @@ -1493,12 +1491,13 @@ int cmd_script(int argc, const char **argv, const char *prefix __maybe_unused) bool show_full_info = false; bool header = false; bool header_only = false; + bool script_started = false; char *rec_script_path = NULL; char *rep_script_path = NULL; struct perf_session *session; char *script_path = NULL; const char **__argv; - int i, j, err; + int i, j, err = 0; struct perf_script script = { .tool = { .sample = process_sample_event, @@ -1510,7 +1509,7 @@ int cmd_script(int argc, const char **argv, const char *prefix __maybe_unused) .attr = process_attr, .tracing_data = perf_event__process_tracing_data, .build_id = perf_event__process_build_id, - .ordered_samples = true, + .ordered_events = true, .ordering_requires_timestamps = true, }, }; @@ -1740,26 +1739,28 @@ int cmd_script(int argc, const char **argv, const char *prefix __maybe_unused) exit(-1); } - if (symbol__init() < 0) - return -1; if (!script_name) setup_pager(); session = perf_session__new(&file, false, &script.tool); if (session == NULL) - return -ENOMEM; + return -1; if (header || header_only) { perf_session__fprintf_info(session, stdout, show_full_info); if (header_only) - return 0; + goto out_delete; } + if (symbol__init(&session->header.env) < 0) + goto out_delete; + script.session = session; if (cpu_list) { - if (perf_session__cpu_bitmap(session, cpu_list, cpu_bitmap)) - return -1; + err = perf_session__cpu_bitmap(session, cpu_list, cpu_bitmap); + if (err < 0) + goto out_delete; } if (!no_callchain) @@ -1774,53 +1775,62 @@ int cmd_script(int argc, const char **argv, const char *prefix __maybe_unused) if (output_set_by_user()) { fprintf(stderr, "custom fields not supported for generated scripts"); - return -1; + err = -EINVAL; + goto out_delete; } input = open(file.path, O_RDONLY); /* input_name */ if (input < 0) { + err = -errno; perror("failed to open file"); - return -1; + goto out_delete; } err = fstat(input, &perf_stat); if (err < 0) { perror("failed to stat file"); - return -1; + goto out_delete; } if (!perf_stat.st_size) { fprintf(stderr, "zero-sized file, nothing to do!\n"); - return 0; + goto out_delete; } scripting_ops = script_spec__lookup(generate_script_lang); if (!scripting_ops) { fprintf(stderr, "invalid language specifier"); - return -1; + err = -ENOENT; + goto out_delete; } err = scripting_ops->generate_script(session->tevent.pevent, "perf-script"); - goto out; + goto out_delete; } if (script_name) { err = scripting_ops->start_script(script_name, argc, argv); if (err) - goto out; + goto out_delete; pr_debug("perf script started with script %s\n\n", script_name); + script_started = true; } err = perf_session__check_output_opt(session); if (err < 0) - goto out; + goto out_delete; err = __cmd_script(&script); + flush_scripting(); + +out_delete: perf_session__delete(session); - cleanup_scripting(); + + if (script_started) + cleanup_scripting(); out: return err; } diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 65a151e36067..b22c62f80078 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; @@ -593,7 +593,7 @@ static int __run_perf_stat(int argc, const char **argv) if (perf_evlist__apply_filters(evsel_list)) { error("failed to set filter with %d (%s)\n", errno, - strerror(errno)); + strerror_r(errno, msg, sizeof(msg))); return -1; } @@ -732,7 +732,7 @@ static void aggr_printout(struct perf_evsel *evsel, int id, int nr) } } -static void nsec_printout(int cpu, int nr, struct perf_evsel *evsel, double avg) +static void nsec_printout(int id, int nr, struct perf_evsel *evsel, double avg) { double msecs = avg / 1e6; const char *fmt_v, *fmt_n; @@ -741,7 +741,7 @@ static void nsec_printout(int cpu, int nr, struct perf_evsel *evsel, double avg) fmt_v = csv_output ? "%.6f%s" : "%18.6f%s"; fmt_n = csv_output ? "%s" : "%-25s"; - aggr_printout(evsel, cpu, nr); + aggr_printout(evsel, id, nr); scnprintf(name, sizeof(name), "%s%s", perf_evsel__name(evsel), csv_output ? "" : " (msec)"); @@ -947,11 +947,12 @@ static void print_ll_cache_misses(int cpu, fprintf(output, " of all LL-cache hits "); } -static void abs_printout(int cpu, int nr, struct perf_evsel *evsel, double avg) +static void abs_printout(int id, int nr, struct perf_evsel *evsel, double avg) { double total, ratio = 0.0, total2; double sc = evsel->scale; const char *fmt; + int cpu = cpu_map__id_to_cpu(id); if (csv_output) { fmt = sc != 1.0 ? "%.2f%s" : "%.0f%s"; @@ -962,7 +963,7 @@ static void abs_printout(int cpu, int nr, struct perf_evsel *evsel, double avg) fmt = sc != 1.0 ? "%18.2f%s" : "%18.0f%s"; } - aggr_printout(evsel, cpu, nr); + aggr_printout(evsel, id, nr); if (aggr_mode == AGGR_GLOBAL) cpu = 0; diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 74db2568b867..35b425b6293f 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(0.5); + svg_io_legenda(); - svg_time_grid(); - svg_legenda(); + draw_io_bars(tchart); + } else { + open_svg(filename, tchart->numcpus, count, tchart->first_time, tchart->last_time); + + svg_time_grid(0); - for (i = 0; i < tchart->numcpus; i++) - svg_cpu_box(i, tchart->max_freq, tchart->turbo_frequency); + svg_legenda(); - 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); + 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, @@ -1121,7 +1605,9 @@ static int __cmd_timechart(struct timechart *tchart, const char *output_name) int ret = -EINVAL; if (session == NULL) - return -ENOMEM; + return -1; + + symbol__init(&session->header.env); (void)perf_header__process_sections(&session->header, perf_data_file__fd(session->file), @@ -1154,6 +1640,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 +1889,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) { @@ -1279,9 +1922,11 @@ int cmd_timechart(int argc, const char **argv, .fork = process_fork_event, .exit = process_exit_event, .sample = process_sample_event, - .ordered_samples = true, + .ordered_events = true, }, .proc_num = 15, + .min_time = 1000000, + .merge_dist = 1000, }; const char *output_name = "output.svg"; const struct option timechart_options[] = { @@ -1303,6 +1948,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 +1967,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() }; @@ -1329,8 +1984,6 @@ int cmd_timechart(int argc, const char **argv, return -1; } - symbol__init(); - if (argc && !strncmp(argv[0], "rec", 3)) { argc = parse_options(argc, argv, record_options, record_usage, PARSE_OPT_STOP_AT_NON_OPTION); @@ -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 377971dc89a3..fc3d55f832ac 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -59,7 +59,7 @@ #include <sys/syscall.h> #include <sys/ioctl.h> -#include <sys/poll.h> +#include <poll.h> #include <sys/prctl.h> #include <sys/wait.h> #include <sys/uio.h> @@ -276,11 +276,17 @@ static void perf_top__print_sym_table(struct perf_top *top) return; } + if (top->zero) { + hists__delete_entries(&top->sym_evsel->hists); + } else { + hists__decay_entries(&top->sym_evsel->hists, + top->hide_user_symbols, + top->hide_kernel_symbols); + } + hists__collapse_resort(&top->sym_evsel->hists, NULL); hists__output_resort(&top->sym_evsel->hists); - hists__decay_entries(&top->sym_evsel->hists, - top->hide_user_symbols, - top->hide_kernel_symbols); + hists__output_recalc_col_len(&top->sym_evsel->hists, top->print_entries - printed); putchar('\n'); @@ -427,18 +433,13 @@ static bool perf_top__handle_keypress(struct perf_top *top, int c) if (!perf_top__key_mapped(top, c)) { struct pollfd stdin_poll = { .fd = 0, .events = POLLIN }; - struct termios tc, save; + struct termios save; perf_top__print_mapped_keys(top); fprintf(stdout, "\nEnter selection, or unmapped key to continue: "); fflush(stdout); - tcgetattr(0, &save); - tc = save; - tc.c_lflag &= ~(ICANON | ECHO); - tc.c_cc[VMIN] = 0; - tc.c_cc[VTIME] = 0; - tcsetattr(0, TCSANOW, &tc); + set_term_quiet_input(&save); poll(&stdin_poll, 1, -1); c = getc(stdin); @@ -542,11 +543,16 @@ static void perf_top__sort_new_samples(void *arg) if (t->evlist->selected != NULL) t->sym_evsel = t->evlist->selected; + if (t->zero) { + hists__delete_entries(&t->sym_evsel->hists); + } else { + hists__decay_entries(&t->sym_evsel->hists, + t->hide_user_symbols, + t->hide_kernel_symbols); + } + hists__collapse_resort(&t->sym_evsel->hists, NULL); hists__output_resort(&t->sym_evsel->hists); - hists__decay_entries(&t->sym_evsel->hists, - t->hide_user_symbols, - t->hide_kernel_symbols); } static void *display_thread_tui(void *arg) @@ -577,23 +583,32 @@ static void *display_thread_tui(void *arg) return NULL; } +static void display_sig(int sig __maybe_unused) +{ + done = 1; +} + +static void display_setup_sig(void) +{ + signal(SIGSEGV, display_sig); + signal(SIGFPE, display_sig); + signal(SIGINT, display_sig); + signal(SIGQUIT, display_sig); + signal(SIGTERM, display_sig); +} + static void *display_thread(void *arg) { struct pollfd stdin_poll = { .fd = 0, .events = POLLIN }; - struct termios tc, save; + struct termios save; struct perf_top *top = arg; int delay_msecs, c; - tcgetattr(0, &save); - tc = save; - tc.c_lflag &= ~(ICANON | ECHO); - tc.c_cc[VMIN] = 0; - tc.c_cc[VTIME] = 0; - + display_setup_sig(); pthread__unblock_sigwinch(); repeat: delay_msecs = top->delay_secs * 1000; - tcsetattr(0, TCSANOW, &tc); + set_term_quiet_input(&save); /* trash return*/ getc(stdin); @@ -620,13 +635,16 @@ repeat: } } + tcsetattr(0, TCSAFLUSH, &save); return NULL; } -static int symbol_filter(struct map *map __maybe_unused, struct symbol *sym) +static int symbol_filter(struct map *map, struct symbol *sym) { const char *name = sym->name; + if (!map->dso->kernel) + return 0; /* * ppc64 uses function descriptors and appends a '.' to the * start of every instruction address. Remove it. @@ -876,7 +894,7 @@ try_again: if (perf_evlist__mmap(evlist, opts->mmap_pages, false) < 0) { ui__error("Failed to mmap with %d (%s)\n", - errno, strerror(errno)); + errno, strerror_r(errno, msg, sizeof(msg))); goto out_err; } @@ -911,7 +929,7 @@ static int __cmd_top(struct perf_top *top) top->session = perf_session__new(NULL, false, NULL); if (top->session == NULL) - return -ENOMEM; + return -1; machines__set_symbol_filter(&top->session->machines, symbol_filter); @@ -946,7 +964,7 @@ static int __cmd_top(struct perf_top *top) perf_evlist__enable(top->evlist); /* Wait for a minimal set of events before starting the snapshot */ - poll(top->evlist->pollfd, top->evlist->nr_fds, 100); + perf_evlist__poll(top->evlist, 100); perf_top__mmap_read(top); @@ -963,7 +981,7 @@ static int __cmd_top(struct perf_top *top) param.sched_priority = top->realtime_prio; if (sched_setscheduler(0, SCHED_FIFO, ¶m)) { ui__error("Could not set realtime priority.\n"); - goto out_delete; + goto out_join; } } @@ -973,10 +991,12 @@ static int __cmd_top(struct perf_top *top) perf_top__mmap_read(top); if (hits == top->samples) - ret = poll(top->evlist->pollfd, top->evlist->nr_fds, 100); + ret = perf_evlist__poll(top->evlist, 100); } ret = 0; +out_join: + pthread_join(thread, NULL); out_delete: perf_session__delete(top->session); top->session = NULL; @@ -1000,10 +1020,8 @@ parse_callchain_opt(const struct option *opt, const char *arg, int unset) static int perf_top_config(const char *var, const char *value, void *cb) { - struct perf_top *top = cb; - if (!strcmp(var, "top.call-graph")) - return record_parse_callchain(value, &top->record_opts); + var = "call-graph.record-mode"; /* fall-through */ if (!strcmp(var, "top.children")) { symbol_conf.cumulate_callchain = perf_config_bool(var, value); return 0; @@ -1122,6 +1140,8 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) "Interleave source code with assembly code (default)"), OPT_BOOLEAN(0, "asm-raw", &symbol_conf.annotate_asm_raw, "Display raw encoding of assembly instructions (default)"), + OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel, + "Enable kernel symbol demangling"), OPT_STRING(0, "objdump", &objdump_path, "path", "objdump binary to use for disassembly and annotations"), OPT_STRING('M', "disassembler-style", &disassembler_style, "disassembler style", @@ -1131,6 +1151,9 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) "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_STRING('w', "column-widths", &symbol_conf.col_width_list_str, + "width[,width...]", + "don't try to adjust column width, use these fixed values"), OPT_END() }; const char * const top_usage[] = { @@ -1217,7 +1240,7 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) symbol_conf.priv_size = sizeof(struct annotation); symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL); - if (symbol__init() < 0) + if (symbol__init(NULL) < 0) return -1; sort__setup_elide(stdout); diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index f954c26de231..09bcf2393910 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -402,6 +402,31 @@ static size_t syscall_arg__scnprintf_mmap_flags(char *bf, size_t size, #define SCA_MMAP_FLAGS syscall_arg__scnprintf_mmap_flags +static size_t syscall_arg__scnprintf_mremap_flags(char *bf, size_t size, + struct syscall_arg *arg) +{ + int printed = 0, flags = arg->val; + +#define P_MREMAP_FLAG(n) \ + if (flags & MREMAP_##n) { \ + printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \ + flags &= ~MREMAP_##n; \ + } + + P_MREMAP_FLAG(MAYMOVE); +#ifdef MREMAP_FIXED + P_MREMAP_FLAG(FIXED); +#endif +#undef P_MREMAP_FLAG + + if (flags) + printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags); + + return printed; +} + +#define SCA_MREMAP_FLAGS syscall_arg__scnprintf_mremap_flags + static size_t syscall_arg__scnprintf_madvise_behavior(char *bf, size_t size, struct syscall_arg *arg) { @@ -1004,6 +1029,7 @@ static struct syscall_fmt { [2] = SCA_MMAP_PROT, /* prot */ }, }, { .name = "mremap", .hexret = true, .arg_scnprintf = { [0] = SCA_HEX, /* addr */ + [3] = SCA_MREMAP_FLAGS, /* flags */ [4] = SCA_HEX, /* new_addr */ }, }, { .name = "munlock", .errmsg = true, .arg_scnprintf = { [0] = SCA_HEX, /* addr */ }, }, @@ -1108,6 +1134,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 +1159,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 +1205,9 @@ fail: return NULL; } +#define TRACE_PFMAJ (1 << 0) +#define TRACE_PFMIN (1 << 1) + struct trace { struct perf_tool tool; struct { @@ -1211,6 +1242,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 +1309,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; } @@ -1378,7 +1411,7 @@ static int trace__tool_process(struct perf_tool *tool, static int trace__symbols_init(struct trace *trace, struct perf_evlist *evlist) { - int err = symbol__init(); + int err = symbol__init(NULL); if (err) return err; @@ -1473,6 +1506,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 +1570,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 +1643,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 +1666,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 +1680,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,9 +1692,10 @@ 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; + long ret; u64 duration = 0; struct thread *thread; int id = perf_evsel__sc_tp_uint(evsel, id, sample); @@ -1687,8 +1724,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) { @@ -1713,9 +1748,9 @@ static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel, if (sc->fmt == NULL) { signed_print: - fprintf(trace->output, ") = %d", ret); + fprintf(trace->output, ") = %ld", ret); } else if (ret < 0 && sc->fmt->errmsg) { - char bf[256]; + char bf[STRERR_BUFSIZE]; const char *emsg = strerror_r(-ret, bf, sizeof(bf)), *e = audit_errno_to_name(-ret); @@ -1723,7 +1758,7 @@ signed_print: } else if (ret == 0 && sc->fmt->timeout) fprintf(trace->output, ") = 0 Timeout"); else if (sc->fmt->hexret) - fprintf(trace->output, ") = %#x", ret); + fprintf(trace->output, ") = %#lx", ret); else goto signed_print; @@ -1735,6 +1770,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 +1778,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 +1805,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 +1892,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 +1910,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 +1937,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 +1946,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]; - return cmd_record(i, rec_argv, NULL); + for (i = 0; i < (unsigned int)argc; i++) + rec_argv[j++] = argv[i]; + + return cmd_record(j, rec_argv, NULL); } static size_t trace__fprintf_thread_summary(struct trace *trace, FILE *fp); @@ -1882,6 +2013,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(); @@ -1889,6 +2044,8 @@ static int trace__run(struct trace *trace, int argc, const char **argv) int err = -1, i; unsigned long before; const bool forks = argc > 0; + bool draining = false; + char sbuf[STRERR_BUFSIZE]; trace->live = true; @@ -1897,10 +2054,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; + + 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; - perf_evlist__add_vfs_getname(evlist); + 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", @@ -1939,7 +2107,8 @@ static int trace__run(struct trace *trace, int argc, const char **argv) err = perf_evlist__mmap(evlist, trace->opts.mmap_pages, false); if (err < 0) { - fprintf(trace->output, "Couldn't mmap the events: %s\n", strerror(errno)); + fprintf(trace->output, "Couldn't mmap the events: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } @@ -1982,7 +2151,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 +2160,7 @@ again: } handler = evsel->handler; - handler(trace, evsel, &sample); + handler(trace, evsel, event, &sample); next_event: perf_evlist__mmap_consume(evlist, i); @@ -2002,8 +2172,12 @@ next_event: if (trace->nr_events == before) { int timeout = done ? 100 : -1; - if (poll(evlist->pollfd, evlist->nr_fds, timeout) > 0) + if (!draining && perf_evlist__poll(evlist, timeout) > 0) { + if (perf_evlist__filter_pollfd(evlist, POLLERR | POLLHUP) == 0) + draining = true; + goto again; + } } else { goto again; } @@ -2068,18 +2242,18 @@ static int trace__replay(struct trace *trace) trace->tool.tracing_data = perf_event__process_tracing_data; trace->tool.build_id = perf_event__process_build_id; - trace->tool.ordered_samples = true; + trace->tool.ordered_events = true; trace->tool.ordering_requires_timestamps = true; /* add tid to output */ trace->multiple_threads = true; - if (symbol__init() < 0) - return -1; - session = perf_session__new(&file, false, &trace->tool); if (session == NULL) - return -ENOMEM; + return -1; + + if (symbol__init(&session->header.env) < 0) + goto out; trace->host = &session->machines.host; @@ -2093,13 +2267,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 +2280,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 +2392,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 +2443,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 +2489,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 +2527,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); + + if (trace.trace_pgfaults) { + trace.opts.sample_address = true; + trace.opts.sample_time = true; + } - argc = parse_options(argc, argv, trace_options, trace_usage, 0); + 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 f30ac5e5d271..58f609198c6d 100644 --- a/tools/perf/config/Makefile +++ b/tools/perf/config/Makefile @@ -116,6 +116,29 @@ ifdef PARSER_DEBUG CFLAGS += -DPARSER_DEBUG endif +ifndef NO_LIBPYTHON + # Try different combinations to accommodate systems that only have + # python[2][-config] in weird combinations but always preferring + # python2 and python2-config as per pep-0394. If we catch a + # python[-config] in version 3, the version check will kill it. + PYTHON2 := $(if $(call get-executable,python2),python2,python) + override PYTHON := $(call get-executable-or-default,PYTHON,$(PYTHON2)) + PYTHON2_CONFIG := \ + $(if $(call get-executable,$(PYTHON)-config),$(PYTHON)-config,python-config) + override PYTHON_CONFIG := \ + $(call get-executable-or-default,PYTHON_CONFIG,$(PYTHON2_CONFIG)) + + PYTHON_CONFIG_SQ := $(call shell-sq,$(PYTHON_CONFIG)) + + PYTHON_EMBED_LDOPTS := $(shell $(PYTHON_CONFIG_SQ) --ldflags 2>/dev/null) + PYTHON_EMBED_CCOPTS := $(shell $(PYTHON_CONFIG_SQ) --cflags 2>/dev/null) + + FEATURE_CHECK_CFLAGS-libpython := $(PYTHON_EMBED_CCOPTS) + FEATURE_CHECK_LDFLAGS-libpython := $(PYTHON_EMBED_LDOPTS) + FEATURE_CHECK_CFLAGS-libpython-version := $(PYTHON_EMBED_CCOPTS) + FEATURE_CHECK_LDFLAGS-libpython-version := $(PYTHON_EMBED_LDOPTS) +endif + CFLAGS += -fno-omit-frame-pointer CFLAGS += -ggdb3 CFLAGS += -funwind-tables @@ -160,6 +183,7 @@ CORE_FEATURE_TESTS = \ backtrace \ dwarf \ fortify-source \ + sync-compare-and-swap \ glibc \ gtk2 \ gtk2-infobar \ @@ -195,6 +219,7 @@ LIB_FEATURE_TESTS = \ VF_FEATURE_TESTS = \ backtrace \ fortify-source \ + sync-compare-and-swap \ gtk2-infobar \ libelf-getphdrnum \ libelf-mmap \ @@ -268,6 +293,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) @@ -345,6 +374,12 @@ ifndef NO_LIBELF endif # NO_DWARF endif # NO_LIBELF +ifeq ($(ARCH),powerpc) + ifndef NO_DWARF + CFLAGS += -DHAVE_SKIP_CALLCHAIN_IDX + endif +endif + ifndef NO_LIBUNWIND ifneq ($(feature-libunwind), 1) msg := $(warning No libunwind found. Please install libunwind-dev[el] >= 1.1 and/or set LIBUNWIND_DIR); @@ -472,21 +507,14 @@ define disable-python_code NO_LIBPYTHON := 1 endef -override PYTHON := \ - $(call get-executable-or-default,PYTHON,python) - -ifndef PYTHON - $(call disable-python,python interpreter) +ifdef NO_LIBPYTHON + $(call disable-python) else - PYTHON_WORD := $(call shell-wordify,$(PYTHON)) - - ifdef NO_LIBPYTHON - $(call disable-python) + ifndef PYTHON + $(call disable-python,python interpreter) else - - override PYTHON_CONFIG := \ - $(call get-executable-or-default,PYTHON_CONFIG,$(PYTHON)-config) + PYTHON_WORD := $(call shell-wordify,$(PYTHON)) ifndef PYTHON_CONFIG $(call disable-python,python-config tool) @@ -590,6 +618,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 @@ -621,11 +653,13 @@ else sysconfdir = $(prefix)/etc ETC_PERFCONFIG = etc/perfconfig endif +ifndef lib ifeq ($(IS_X86_64),1) lib = lib64 else lib = lib endif +endif # lib libdir = $(prefix)/$(lib) # Shell quote (do not use $(call) to accommodate ancient setups); diff --git a/tools/perf/config/feature-checks/Makefile b/tools/perf/config/feature-checks/Makefile index 64c84e5f0514..72ab2984718e 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 \ @@ -100,25 +101,11 @@ FLAGS_PERL_EMBED=$(PERL_EMBED_CCOPTS) $(PERL_EMBED_LDOPTS) test-libperl.bin: $(BUILD) $(FLAGS_PERL_EMBED) -override PYTHON := python -override PYTHON_CONFIG := python-config - -escape-for-shell-sq = $(subst ','\'',$(1)) -shell-sq = '$(escape-for-shell-sq)' - -PYTHON_CONFIG_SQ = $(call shell-sq,$(PYTHON_CONFIG)) - -PYTHON_EMBED_LDOPTS = $(shell $(PYTHON_CONFIG_SQ) --ldflags 2>/dev/null) -PYTHON_EMBED_LDFLAGS = $(call strip-libs,$(PYTHON_EMBED_LDOPTS)) -PYTHON_EMBED_LIBADD = $(call grep-libs,$(PYTHON_EMBED_LDOPTS)) -PYTHON_EMBED_CCOPTS = $(shell $(PYTHON_CONFIG_SQ) --cflags 2>/dev/null) -FLAGS_PYTHON_EMBED = $(PYTHON_EMBED_CCOPTS) $(PYTHON_EMBED_LDOPTS) - test-libpython.bin: - $(BUILD) $(FLAGS_PYTHON_EMBED) + $(BUILD) test-libpython-version.bin: - $(BUILD) $(FLAGS_PYTHON_EMBED) + $(BUILD) test-libbfd.bin: $(BUILD) -DPACKAGE='"perf"' -lbfd -lz -liberty -ldl @@ -141,6 +128,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 fe5c1e5c952f..a7d022e161c0 100644 --- a/tools/perf/config/feature-checks/test-all.c +++ b/tools/perf/config/feature-checks/test-all.c @@ -89,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(); @@ -111,6 +115,7 @@ int main(int argc, char *argv[]) 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-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/config/utilities.mak b/tools/perf/config/utilities.mak index 4d985e0f03f5..7076a62d0ff7 100644 --- a/tools/perf/config/utilities.mak +++ b/tools/perf/config/utilities.mak @@ -132,7 +132,7 @@ endef # # Usage: bool-value = $(call is-absolute,path) # -is-absolute = $(shell echo $(shell-sq) | grep ^/ -q && echo y) +is-absolute = $(shell echo $(shell-sq) | grep -q ^/ && echo y) # lookup # diff --git a/tools/perf/perf-sys.h b/tools/perf/perf-sys.h index 5268a1481d23..937e4324ad94 100644 --- a/tools/perf/perf-sys.h +++ b/tools/perf/perf-sys.h @@ -54,6 +54,7 @@ #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__ diff --git a/tools/perf/perf-with-kcore.sh b/tools/perf/perf-with-kcore.sh new file mode 100644 index 000000000000..c7ff90a90e4e --- /dev/null +++ b/tools/perf/perf-with-kcore.sh @@ -0,0 +1,259 @@ +#!/bin/bash +# perf-with-kcore: use perf with a copy of kcore +# Copyright (c) 2014, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. + +set -e + +usage() +{ + echo "Usage: perf-with-kcore <perf sub-command> <perf.data directory> [<sub-command options> [ -- <workload>]]" >&2 + echo " <perf sub-command> can be record, script, report or inject" >&2 + echo " or: perf-with-kcore fix_buildid_cache_permissions" >&2 + exit 1 +} + +find_perf() +{ + if [ -n "$PERF" ] ; then + return + fi + PERF=`which perf || true` + if [ -z "$PERF" ] ; then + echo "Failed to find perf" >&2 + exit 1 + fi + if [ ! -x "$PERF" ] ; then + echo "Failed to find perf" >&2 + exit 1 + fi + echo "Using $PERF" + "$PERF" version +} + +copy_kcore() +{ + echo "Copying kcore" + + if [ $EUID -eq 0 ] ; then + SUDO="" + else + SUDO="sudo" + fi + + rm -f perf.data.junk + ("$PERF" record -o perf.data.junk $PERF_OPTIONS -- sleep 60) >/dev/null 2>/dev/null & + PERF_PID=$! + + # Need to make sure that perf has started + sleep 1 + + KCORE=$(($SUDO "$PERF" buildid-cache -v -f -k /proc/kcore >/dev/null) 2>&1) + case "$KCORE" in + "kcore added to build-id cache directory "*) + KCORE_DIR=${KCORE#"kcore added to build-id cache directory "} + ;; + *) + kill $PERF_PID + wait >/dev/null 2>/dev/null || true + rm perf.data.junk + echo "$KCORE" + echo "Failed to find kcore" >&2 + exit 1 + ;; + esac + + kill $PERF_PID + wait >/dev/null 2>/dev/null || true + rm perf.data.junk + + $SUDO cp -a "$KCORE_DIR" "$(pwd)/$PERF_DATA_DIR" + $SUDO rm -f "$KCORE_DIR/kcore" + $SUDO rm -f "$KCORE_DIR/kallsyms" + $SUDO rm -f "$KCORE_DIR/modules" + $SUDO rmdir "$KCORE_DIR" + + KCORE_DIR_BASENAME=$(basename "$KCORE_DIR") + KCORE_DIR="$(pwd)/$PERF_DATA_DIR/$KCORE_DIR_BASENAME" + + $SUDO chown $UID "$KCORE_DIR" + $SUDO chown $UID "$KCORE_DIR/kcore" + $SUDO chown $UID "$KCORE_DIR/kallsyms" + $SUDO chown $UID "$KCORE_DIR/modules" + + $SUDO chgrp $GROUPS "$KCORE_DIR" + $SUDO chgrp $GROUPS "$KCORE_DIR/kcore" + $SUDO chgrp $GROUPS "$KCORE_DIR/kallsyms" + $SUDO chgrp $GROUPS "$KCORE_DIR/modules" + + ln -s "$KCORE_DIR_BASENAME" "$PERF_DATA_DIR/kcore_dir" +} + +fix_buildid_cache_permissions() +{ + if [ $EUID -ne 0 ] ; then + echo "This script must be run as root via sudo " >&2 + exit 1 + fi + + if [ -z "$SUDO_USER" ] ; then + echo "This script must be run via sudo" >&2 + exit 1 + fi + + USER_HOME=$(bash <<< "echo ~$SUDO_USER") + + if [ "$HOME" != "$USER_HOME" ] ; then + echo "Fix unnecessary because root has a home: $HOME" >&2 + exit 1 + fi + + echo "Fixing buildid cache permissions" + + find "$USER_HOME/.debug" -xdev -type d ! -user "$SUDO_USER" -ls -exec chown "$SUDO_USER" \{\} \; + find "$USER_HOME/.debug" -xdev -type f -links 1 ! -user "$SUDO_USER" -ls -exec chown "$SUDO_USER" \{\} \; + find "$USER_HOME/.debug" -xdev -type l ! -user "$SUDO_USER" -ls -exec chown -h "$SUDO_USER" \{\} \; + + if [ -n "$SUDO_GID" ] ; then + find "$USER_HOME/.debug" -xdev -type d ! -group "$SUDO_GID" -ls -exec chgrp "$SUDO_GID" \{\} \; + find "$USER_HOME/.debug" -xdev -type f -links 1 ! -group "$SUDO_GID" -ls -exec chgrp "$SUDO_GID" \{\} \; + find "$USER_HOME/.debug" -xdev -type l ! -group "$SUDO_GID" -ls -exec chgrp -h "$SUDO_GID" \{\} \; + fi + + echo "Done" +} + +check_buildid_cache_permissions() +{ + if [ $EUID -eq 0 ] ; then + return + fi + + PERMISSIONS_OK+=$(find "$HOME/.debug" -xdev -type d ! -user "$USER" -print -quit) + PERMISSIONS_OK+=$(find "$HOME/.debug" -xdev -type f -links 1 ! -user "$USER" -print -quit) + PERMISSIONS_OK+=$(find "$HOME/.debug" -xdev -type l ! -user "$USER" -print -quit) + + PERMISSIONS_OK+=$(find "$HOME/.debug" -xdev -type d ! -group "$GROUPS" -print -quit) + PERMISSIONS_OK+=$(find "$HOME/.debug" -xdev -type f -links 1 ! -group "$GROUPS" -print -quit) + PERMISSIONS_OK+=$(find "$HOME/.debug" -xdev -type l ! -group "$GROUPS" -print -quit) + + if [ -n "$PERMISSIONS_OK" ] ; then + echo "*** WARNING *** buildid cache permissions may need fixing" >&2 + fi +} + +record() +{ + echo "Recording" + + if [ $EUID -ne 0 ] ; then + + if [ "$(cat /proc/sys/kernel/kptr_restrict)" -ne 0 ] ; then + echo "*** WARNING *** /proc/sys/kernel/kptr_restrict prevents access to kernel addresses" >&2 + fi + + if echo "$PERF_OPTIONS" | grep -q ' -a \|^-a \| -a$\|^-a$\| --all-cpus \|^--all-cpus \| --all-cpus$\|^--all-cpus$' ; then + echo "*** WARNING *** system-wide tracing without root access will not be able to read all necessary information from /proc" >&2 + fi + + if echo "$PERF_OPTIONS" | grep -q 'intel_pt\|intel_bts\| -I\|^-I' ; then + if [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt -1 ] ; then + echo "*** WARNING *** /proc/sys/kernel/perf_event_paranoid restricts buffer size and tracepoint (sched_switch) use" >&2 + fi + + if echo "$PERF_OPTIONS" | grep -q ' --per-thread \|^--per-thread \| --per-thread$\|^--per-thread$' ; then + true + elif echo "$PERF_OPTIONS" | grep -q ' -t \|^-t \| -t$\|^-t$' ; then + true + elif [ ! -r /sys/kernel/debug -o ! -x /sys/kernel/debug ] ; then + echo "*** WARNING *** /sys/kernel/debug permissions prevent tracepoint (sched_switch) use" >&2 + fi + fi + fi + + if [ -z "$1" ] ; then + echo "Workload is required for recording" >&2 + usage + fi + + if [ -e "$PERF_DATA_DIR" ] ; then + echo "'$PERF_DATA_DIR' exists" >&2 + exit 1 + fi + + find_perf + + mkdir "$PERF_DATA_DIR" + + echo "$PERF record -o $PERF_DATA_DIR/perf.data $PERF_OPTIONS -- $*" + "$PERF" record -o "$PERF_DATA_DIR/perf.data" $PERF_OPTIONS -- $* || true + + if rmdir "$PERF_DATA_DIR" > /dev/null 2>/dev/null ; then + exit 1 + fi + + copy_kcore + + echo "Done" +} + +subcommand() +{ + find_perf + check_buildid_cache_permissions + echo "$PERF $PERF_SUB_COMMAND -i $PERF_DATA_DIR/perf.data --kallsyms=$PERF_DATA_DIR/kcore_dir/kallsyms $*" + "$PERF" $PERF_SUB_COMMAND -i "$PERF_DATA_DIR/perf.data" "--kallsyms=$PERF_DATA_DIR/kcore_dir/kallsyms" $* +} + +if [ "$1" = "fix_buildid_cache_permissions" ] ; then + fix_buildid_cache_permissions + exit 0 +fi + +PERF_SUB_COMMAND=$1 +PERF_DATA_DIR=$2 +shift || true +shift || true + +if [ -z "$PERF_SUB_COMMAND" ] ; then + usage +fi + +if [ -z "$PERF_DATA_DIR" ] ; then + usage +fi + +case "$PERF_SUB_COMMAND" in +"record") + while [ "$1" != "--" ] ; do + PERF_OPTIONS+="$1 " + shift || break + done + if [ "$1" != "--" ] ; then + echo "Options and workload are required for recording" >&2 + usage + fi + shift + record $* +;; +"script") + subcommand $* +;; +"report") + subcommand $* +;; +"inject") + subcommand $* +;; +*) + usage +;; +esac diff --git a/tools/perf/perf.c b/tools/perf/perf.c index 95c58fc15284..452a8474d29d 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); @@ -302,6 +313,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) int status; struct stat st; const char *prefix; + char sbuf[STRERR_BUFSIZE]; prefix = NULL; if (p->option & RUN_SETUP) @@ -332,7 +344,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) status = 1; /* Check for ENOSPC and EIO errors.. */ if (fflush(stdout)) { - fprintf(stderr, "write failure on standard output: %s", strerror(errno)); + fprintf(stderr, "write failure on standard output: %s", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out; } if (ferror(stdout)) { @@ -340,7 +353,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) goto out; } if (fclose(stdout)) { - fprintf(stderr, "close failed on standard output: %s", strerror(errno)); + fprintf(stderr, "close failed on standard output: %s", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out; } status = 0; @@ -455,6 +469,7 @@ void pthread__unblock_sigwinch(void) int main(int argc, const char **argv) { const char *cmd; + char sbuf[STRERR_BUFSIZE]; /* The page_size is placed in util object. */ page_size = sysconf(_SC_PAGE_SIZE); @@ -550,7 +565,7 @@ int main(int argc, const char **argv) } fprintf(stderr, "Failed to run command '%s': %s\n", - cmd, strerror(errno)); + cmd, strerror_r(errno, sbuf, sizeof(sbuf))); out: return 1; } diff --git a/tools/perf/perf.h b/tools/perf/perf.h index 510c65f72858..220d44e44c1b 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h @@ -41,8 +41,6 @@ void pthread__unblock_sigwinch(void); struct record_opts { struct target target; - int call_graph; - bool call_graph_enabled; bool group; bool inherit_stat; bool no_buffering; @@ -60,7 +58,6 @@ struct record_opts { u64 branch_stack; u64 default_interval; u64 user_interval; - u16 stack_dump_size; bool sample_transaction; unsigned initial_delay; }; 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/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 6f8b01bc6033..ac655b0700e7 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -154,6 +154,18 @@ static struct test { .func = test__hists_cumulate, }, { + .desc = "Test tracking with sched_switch", + .func = test__switch_tracking, + }, + { + .desc = "Filter fds with revents mask in a fdarray", + .func = test__fdarray__filter, + }, + { + .desc = "Add fd to a fdarray, making it autogrow", + .func = test__fdarray__add, + }, + { .func = NULL, }, }; @@ -185,9 +197,11 @@ static bool perf_test__matches(int curr, int argc, const char *argv[]) static int run_test(struct test *test) { int status, err = -1, child = fork(); + char sbuf[STRERR_BUFSIZE]; if (child < 0) { - pr_err("failed to fork test: %s\n", strerror(errno)); + pr_err("failed to fork test: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); return -1; } @@ -297,7 +311,7 @@ int cmd_test(int argc, const char **argv, const char *prefix __maybe_unused) symbol_conf.sort_by_name = true; symbol_conf.try_vmlinux_path = true; - if (symbol__init() < 0) + if (symbol__init(NULL) < 0) return -1; if (skip != NULL) diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 630808cd7cc2..caaf37f079b1 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -10,6 +10,7 @@ #include "machine.h" #include "symbol.h" #include "tests.h" +#include "debug.h" static char *test_file(int size) { 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 35d7fdb2328d..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) diff --git a/tools/perf/tests/fdarray.c b/tools/perf/tests/fdarray.c new file mode 100644 index 000000000000..d24b837951d4 --- /dev/null +++ b/tools/perf/tests/fdarray.c @@ -0,0 +1,174 @@ +#include <api/fd/array.h> +#include "util/debug.h" +#include "tests/tests.h" + +static void fdarray__init_revents(struct fdarray *fda, short revents) +{ + int fd; + + fda->nr = fda->nr_alloc; + + for (fd = 0; fd < fda->nr; ++fd) { + fda->entries[fd].fd = fda->nr - fd; + fda->entries[fd].revents = revents; + } +} + +static int fdarray__fprintf_prefix(struct fdarray *fda, const char *prefix, FILE *fp) +{ + int printed = 0; + + if (!verbose) + return 0; + + printed += fprintf(fp, "\n%s: ", prefix); + return printed + fdarray__fprintf(fda, fp); +} + +int test__fdarray__filter(void) +{ + int nr_fds, expected_fd[2], fd, err = TEST_FAIL; + struct fdarray *fda = fdarray__new(5, 5); + + if (fda == NULL) { + pr_debug("\nfdarray__new() failed!"); + goto out; + } + + fdarray__init_revents(fda, POLLIN); + nr_fds = fdarray__filter(fda, POLLHUP, NULL); + if (nr_fds != fda->nr_alloc) { + pr_debug("\nfdarray__filter()=%d != %d shouldn't have filtered anything", + nr_fds, fda->nr_alloc); + goto out_delete; + } + + fdarray__init_revents(fda, POLLHUP); + nr_fds = fdarray__filter(fda, POLLHUP, NULL); + if (nr_fds != 0) { + pr_debug("\nfdarray__filter()=%d != %d, should have filtered all fds", + nr_fds, fda->nr_alloc); + goto out_delete; + } + + fdarray__init_revents(fda, POLLHUP); + fda->entries[2].revents = POLLIN; + expected_fd[0] = fda->entries[2].fd; + + pr_debug("\nfiltering all but fda->entries[2]:"); + fdarray__fprintf_prefix(fda, "before", stderr); + nr_fds = fdarray__filter(fda, POLLHUP, NULL); + fdarray__fprintf_prefix(fda, " after", stderr); + if (nr_fds != 1) { + pr_debug("\nfdarray__filter()=%d != 1, should have left just one event", nr_fds); + goto out_delete; + } + + if (fda->entries[0].fd != expected_fd[0]) { + pr_debug("\nfda->entries[0].fd=%d != %d\n", + fda->entries[0].fd, expected_fd[0]); + goto out_delete; + } + + fdarray__init_revents(fda, POLLHUP); + fda->entries[0].revents = POLLIN; + expected_fd[0] = fda->entries[0].fd; + fda->entries[3].revents = POLLIN; + expected_fd[1] = fda->entries[3].fd; + + pr_debug("\nfiltering all but (fda->entries[0], fda->entries[3]):"); + fdarray__fprintf_prefix(fda, "before", stderr); + nr_fds = fdarray__filter(fda, POLLHUP, NULL); + fdarray__fprintf_prefix(fda, " after", stderr); + if (nr_fds != 2) { + pr_debug("\nfdarray__filter()=%d != 2, should have left just two events", + nr_fds); + goto out_delete; + } + + for (fd = 0; fd < 2; ++fd) { + if (fda->entries[fd].fd != expected_fd[fd]) { + pr_debug("\nfda->entries[%d].fd=%d != %d\n", fd, + fda->entries[fd].fd, expected_fd[fd]); + goto out_delete; + } + } + + pr_debug("\n"); + + err = 0; +out_delete: + fdarray__delete(fda); +out: + return err; +} + +int test__fdarray__add(void) +{ + int err = TEST_FAIL; + struct fdarray *fda = fdarray__new(2, 2); + + if (fda == NULL) { + pr_debug("\nfdarray__new() failed!"); + goto out; + } + +#define FDA_CHECK(_idx, _fd, _revents) \ + if (fda->entries[_idx].fd != _fd) { \ + pr_debug("\n%d: fda->entries[%d](%d) != %d!", \ + __LINE__, _idx, fda->entries[1].fd, _fd); \ + goto out_delete; \ + } \ + if (fda->entries[_idx].events != (_revents)) { \ + pr_debug("\n%d: fda->entries[%d].revents(%d) != %d!", \ + __LINE__, _idx, fda->entries[_idx].fd, _revents); \ + goto out_delete; \ + } + +#define FDA_ADD(_idx, _fd, _revents, _nr) \ + if (fdarray__add(fda, _fd, _revents) < 0) { \ + pr_debug("\n%d: fdarray__add(fda, %d, %d) failed!", \ + __LINE__,_fd, _revents); \ + goto out_delete; \ + } \ + if (fda->nr != _nr) { \ + pr_debug("\n%d: fdarray__add(fda, %d, %d)=%d != %d", \ + __LINE__,_fd, _revents, fda->nr, _nr); \ + goto out_delete; \ + } \ + FDA_CHECK(_idx, _fd, _revents) + + FDA_ADD(0, 1, POLLIN, 1); + FDA_ADD(1, 2, POLLERR, 2); + + fdarray__fprintf_prefix(fda, "before growing array", stderr); + + FDA_ADD(2, 35, POLLHUP, 3); + + if (fda->entries == NULL) { + pr_debug("\nfdarray__add(fda, 35, POLLHUP) should have allocated fda->pollfd!"); + goto out_delete; + } + + fdarray__fprintf_prefix(fda, "after 3rd add", stderr); + + FDA_ADD(3, 88, POLLIN | POLLOUT, 4); + + fdarray__fprintf_prefix(fda, "after 4th add", stderr); + + FDA_CHECK(0, 1, POLLIN); + FDA_CHECK(1, 2, POLLERR); + FDA_CHECK(2, 35, POLLHUP); + FDA_CHECK(3, 88, POLLIN | POLLOUT); + +#undef FDA_ADD +#undef FDA_CHECK + + pr_debug("\n"); + + err = 0; +out_delete: + fdarray__delete(fda); +out: + return err; +} diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c index 142263492f6f..9b9622a33932 100644 --- a/tools/perf/tests/mmap-basic.c +++ b/tools/perf/tests/mmap-basic.c @@ -31,6 +31,7 @@ int test__basic_mmap(void) unsigned int nr_events[nsyscalls], expected_nr_events[nsyscalls], i, j; struct perf_evsel *evsels[nsyscalls], *evsel; + char sbuf[STRERR_BUFSIZE]; threads = thread_map__new(-1, getpid(), UINT_MAX); if (threads == NULL) { @@ -49,7 +50,7 @@ int test__basic_mmap(void) sched_setaffinity(0, sizeof(cpu_set), &cpu_set); if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { pr_debug("sched_setaffinity() failed on CPU %d: %s ", - cpus->map[0], strerror(errno)); + cpus->map[0], strerror_r(errno, sbuf, sizeof(sbuf))); goto out_free_cpus; } @@ -79,7 +80,7 @@ int test__basic_mmap(void) if (perf_evsel__open(evsels[i], cpus, threads) < 0) { pr_debug("failed to open counter: %s, " "tweak /proc/sys/kernel/perf_event_paranoid?\n", - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } @@ -89,7 +90,7 @@ int test__basic_mmap(void) if (perf_evlist__mmap(evlist, 128, true) < 0) { pr_debug("failed to mmap events: %d (%s)\n", errno, - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } diff --git a/tools/perf/tests/open-syscall-all-cpus.c b/tools/perf/tests/open-syscall-all-cpus.c index 5fecdbd2f5f7..8fa82d1700c7 100644 --- a/tools/perf/tests/open-syscall-all-cpus.c +++ b/tools/perf/tests/open-syscall-all-cpus.c @@ -12,6 +12,7 @@ int test__open_syscall_event_on_all_cpus(void) unsigned int nr_open_calls = 111, i; cpu_set_t cpu_set; struct thread_map *threads = thread_map__new(-1, getpid(), UINT_MAX); + char sbuf[STRERR_BUFSIZE]; if (threads == NULL) { pr_debug("thread_map__new\n"); @@ -35,7 +36,7 @@ int test__open_syscall_event_on_all_cpus(void) if (perf_evsel__open(evsel, cpus, threads) < 0) { pr_debug("failed to open counter: %s, " "tweak /proc/sys/kernel/perf_event_paranoid?\n", - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_evsel_delete; } @@ -56,7 +57,7 @@ int test__open_syscall_event_on_all_cpus(void) if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { pr_debug("sched_setaffinity() failed on CPU %d: %s ", cpus->map[cpu], - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_close_fd; } for (i = 0; i < ncalls; ++i) { diff --git a/tools/perf/tests/open-syscall-tp-fields.c b/tools/perf/tests/open-syscall-tp-fields.c index c505ef2af245..127dcae0b760 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) { @@ -21,6 +22,7 @@ int test__syscall_open_tp_fields(void) struct perf_evlist *evlist = perf_evlist__new(); struct perf_evsel *evsel; int err = -1, i, nr_events = 0, nr_polls = 0; + char sbuf[STRERR_BUFSIZE]; if (evlist == NULL) { pr_debug("%s: perf_evlist__new\n", __func__); @@ -47,13 +49,15 @@ int test__syscall_open_tp_fields(void) err = perf_evlist__open(evlist); if (err < 0) { - pr_debug("perf_evlist__open: %s\n", strerror(errno)); + pr_debug("perf_evlist__open: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } err = perf_evlist__mmap(evlist, UINT_MAX, false); if (err < 0) { - pr_debug("perf_evlist__mmap: %s\n", strerror(errno)); + pr_debug("perf_evlist__mmap: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } @@ -101,7 +105,7 @@ int test__syscall_open_tp_fields(void) } if (nr_events == before) - poll(evlist->pollfd, evlist->nr_fds, 10); + perf_evlist__poll(evlist, 10); if (++nr_polls > 5) { pr_debug("%s: no events!\n", __func__); diff --git a/tools/perf/tests/open-syscall.c b/tools/perf/tests/open-syscall.c index c1dc7d25f38c..a33b2daae40f 100644 --- a/tools/perf/tests/open-syscall.c +++ b/tools/perf/tests/open-syscall.c @@ -9,6 +9,7 @@ int test__open_syscall_event(void) struct perf_evsel *evsel; unsigned int nr_open_calls = 111, i; struct thread_map *threads = thread_map__new(-1, getpid(), UINT_MAX); + char sbuf[STRERR_BUFSIZE]; if (threads == NULL) { pr_debug("thread_map__new\n"); @@ -24,7 +25,7 @@ int test__open_syscall_event(void) if (perf_evsel__open_per_thread(evsel, threads) < 0) { pr_debug("failed to open counter: %s, " "tweak /proc/sys/kernel/perf_event_paranoid?\n", - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_evsel_delete; } diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c index deba66955f8c..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 | \ diff --git a/tools/perf/tests/parse-no-sample-id-all.c b/tools/perf/tests/parse-no-sample-id-all.c index 905019f9b740..2c63ea658541 100644 --- a/tools/perf/tests/parse-no-sample-id-all.c +++ b/tools/perf/tests/parse-no-sample-id-all.c @@ -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-record.c b/tools/perf/tests/perf-record.c index aca1a83dd13a..7a228a2a070b 100644 --- a/tools/perf/tests/perf-record.c +++ b/tools/perf/tests/perf-record.c @@ -59,6 +59,7 @@ int test__PERF_RECORD(void) int err = -1, errs = 0, i, wakeups = 0; u32 cpu; int total_events = 0, nr_events[PERF_RECORD_MAX] = { 0, }; + char sbuf[STRERR_BUFSIZE]; if (evlist == NULL || argv == NULL) { pr_debug("Not enough memory to create evlist\n"); @@ -100,7 +101,8 @@ int test__PERF_RECORD(void) err = sched__get_first_possible_cpu(evlist->workload.pid, &cpu_mask); if (err < 0) { - pr_debug("sched__get_first_possible_cpu: %s\n", strerror(errno)); + pr_debug("sched__get_first_possible_cpu: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } @@ -110,7 +112,8 @@ int test__PERF_RECORD(void) * So that we can check perf_sample.cpu on all the samples. */ if (sched_setaffinity(evlist->workload.pid, cpu_mask_size, &cpu_mask) < 0) { - pr_debug("sched_setaffinity: %s\n", strerror(errno)); + pr_debug("sched_setaffinity: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } @@ -120,7 +123,8 @@ int test__PERF_RECORD(void) */ err = perf_evlist__open(evlist); if (err < 0) { - pr_debug("perf_evlist__open: %s\n", strerror(errno)); + pr_debug("perf_evlist__open: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } @@ -131,7 +135,8 @@ int test__PERF_RECORD(void) */ err = perf_evlist__mmap(evlist, opts.mmap_pages, false); if (err < 0) { - pr_debug("perf_evlist__mmap: %s\n", strerror(errno)); + pr_debug("perf_evlist__mmap: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } @@ -263,7 +268,7 @@ int test__PERF_RECORD(void) * perf_event_attr.wakeup_events, just PERF_EVENT_SAMPLE does. */ if (total_events == before && false) - poll(evlist->pollfd, evlist->nr_fds, -1); + perf_evlist__poll(evlist, -1); sleep(1); if (++wakeups > 5) { diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c index 3b7cd4d32dcb..f238442b238a 100644 --- a/tools/perf/tests/perf-time-to-tsc.c +++ b/tools/perf/tests/perf-time-to-tsc.c @@ -8,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"); \ @@ -26,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/pmu.c b/tools/perf/tests/pmu.c index 12b322fa3475..eeb68bb1972d 100644 --- a/tools/perf/tests/pmu.c +++ b/tools/perf/tests/pmu.c @@ -152,7 +152,7 @@ int test__pmu(void) if (ret) break; - ret = perf_pmu__config_terms(&formats, &attr, terms); + ret = perf_pmu__config_terms(&formats, &attr, terms, false); if (ret) break; diff --git a/tools/perf/tests/rdpmc.c b/tools/perf/tests/rdpmc.c index e59143fd9e71..d31f2c4d9f64 100644 --- a/tools/perf/tests/rdpmc.c +++ b/tools/perf/tests/rdpmc.c @@ -6,6 +6,7 @@ #include "perf.h" #include "debug.h" #include "tests.h" +#include "cloexec.h" #if defined(__x86_64__) || defined(__i386__) @@ -99,22 +100,25 @@ static int __test__rdpmc(void) }; u64 delta_sum = 0; struct sigaction sa; + char sbuf[STRERR_BUFSIZE]; sigfillset(&sa.sa_mask); 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)); + "with %d (%s)\n", fd, + strerror_r(errno, sbuf, sizeof(sbuf))); return -1; } addr = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0); if (addr == (void *)(-1)) { pr_err("Error: mmap() syscall returned with (%s)\n", - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_close; } diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c index 7ae8d17db3d9..ca292f9a4ae2 100644 --- a/tools/perf/tests/sample-parsing.c +++ b/tools/perf/tests/sample-parsing.c @@ -4,6 +4,7 @@ #include "util.h" #include "event.h" #include "evsel.h" +#include "debug.h" #include "tests.h" diff --git a/tools/perf/tests/sw-clock.c b/tools/perf/tests/sw-clock.c index 983d6b8562a8..1aa21c90731b 100644 --- a/tools/perf/tests/sw-clock.c +++ b/tools/perf/tests/sw-clock.c @@ -22,6 +22,7 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id) volatile int tmp = 0; u64 total_periods = 0; int nr_samples = 0; + char sbuf[STRERR_BUFSIZE]; union perf_event *event; struct perf_evsel *evsel; struct perf_evlist *evlist; @@ -62,14 +63,15 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id) err = -errno; pr_debug("Couldn't open evlist: %s\nHint: check %s, using %" PRIu64 " in this test.\n", - strerror(errno), knob, (u64)attr.sample_freq); + strerror_r(errno, sbuf, sizeof(sbuf)), + knob, (u64)attr.sample_freq); goto out_delete_evlist; } err = perf_evlist__mmap(evlist, 128, true); if (err < 0) { pr_debug("failed to mmap event: %d (%s)\n", errno, - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } diff --git a/tools/perf/tests/switch-tracking.c b/tools/perf/tests/switch-tracking.c new file mode 100644 index 000000000000..cc68648c7c55 --- /dev/null +++ b/tools/perf/tests/switch-tracking.c @@ -0,0 +1,572 @@ +#include <sys/time.h> +#include <sys/prctl.h> +#include <time.h> +#include <stdlib.h> + +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tests.h" + +static int spin_sleep(void) +{ + struct timeval start, now, diff, maxtime; + struct timespec ts; + int err, i; + + maxtime.tv_sec = 0; + maxtime.tv_usec = 50000; + + err = gettimeofday(&start, NULL); + if (err) + return err; + + /* Spin for 50ms */ + while (1) { + for (i = 0; i < 1000; i++) + barrier(); + + err = gettimeofday(&now, NULL); + if (err) + return err; + + timersub(&now, &start, &diff); + if (timercmp(&diff, &maxtime, > /* For checkpatch */)) + break; + } + + ts.tv_nsec = 50 * 1000 * 1000; + ts.tv_sec = 0; + + /* Sleep for 50ms */ + err = nanosleep(&ts, NULL); + if (err == EINTR) + err = 0; + + return err; +} + +struct switch_tracking { + struct perf_evsel *switch_evsel; + struct perf_evsel *cycles_evsel; + pid_t *tids; + int nr_tids; + int comm_seen[4]; + int cycles_before_comm_1; + int cycles_between_comm_2_and_comm_3; + int cycles_after_comm_4; +}; + +static int check_comm(struct switch_tracking *switch_tracking, + union perf_event *event, const char *comm, int nr) +{ + if (event->header.type == PERF_RECORD_COMM && + (pid_t)event->comm.pid == getpid() && + (pid_t)event->comm.tid == getpid() && + strcmp(event->comm.comm, comm) == 0) { + if (switch_tracking->comm_seen[nr]) { + pr_debug("Duplicate comm event\n"); + return -1; + } + switch_tracking->comm_seen[nr] = 1; + pr_debug3("comm event: %s nr: %d\n", event->comm.comm, nr); + return 1; + } + return 0; +} + +static int check_cpu(struct switch_tracking *switch_tracking, int cpu) +{ + int i, nr = cpu + 1; + + if (cpu < 0) + return -1; + + if (!switch_tracking->tids) { + switch_tracking->tids = calloc(nr, sizeof(pid_t)); + if (!switch_tracking->tids) + return -1; + for (i = 0; i < nr; i++) + switch_tracking->tids[i] = -1; + switch_tracking->nr_tids = nr; + return 0; + } + + if (cpu >= switch_tracking->nr_tids) { + void *addr; + + addr = realloc(switch_tracking->tids, nr * sizeof(pid_t)); + if (!addr) + return -1; + switch_tracking->tids = addr; + for (i = switch_tracking->nr_tids; i < nr; i++) + switch_tracking->tids[i] = -1; + switch_tracking->nr_tids = nr; + return 0; + } + + return 0; +} + +static int process_sample_event(struct perf_evlist *evlist, + union perf_event *event, + struct switch_tracking *switch_tracking) +{ + struct perf_sample sample; + struct perf_evsel *evsel; + pid_t next_tid, prev_tid; + int cpu, err; + + if (perf_evlist__parse_sample(evlist, event, &sample)) { + pr_debug("perf_evlist__parse_sample failed\n"); + return -1; + } + + evsel = perf_evlist__id2evsel(evlist, sample.id); + if (evsel == switch_tracking->switch_evsel) { + next_tid = perf_evsel__intval(evsel, &sample, "next_pid"); + prev_tid = perf_evsel__intval(evsel, &sample, "prev_pid"); + cpu = sample.cpu; + pr_debug3("sched_switch: cpu: %d prev_tid %d next_tid %d\n", + cpu, prev_tid, next_tid); + err = check_cpu(switch_tracking, cpu); + if (err) + return err; + /* + * Check for no missing sched_switch events i.e. that the + * evsel->system_wide flag has worked. + */ + if (switch_tracking->tids[cpu] != -1 && + switch_tracking->tids[cpu] != prev_tid) { + pr_debug("Missing sched_switch events\n"); + return -1; + } + switch_tracking->tids[cpu] = next_tid; + } + + if (evsel == switch_tracking->cycles_evsel) { + pr_debug3("cycles event\n"); + if (!switch_tracking->comm_seen[0]) + switch_tracking->cycles_before_comm_1 = 1; + if (switch_tracking->comm_seen[1] && + !switch_tracking->comm_seen[2]) + switch_tracking->cycles_between_comm_2_and_comm_3 = 1; + if (switch_tracking->comm_seen[3]) + switch_tracking->cycles_after_comm_4 = 1; + } + + return 0; +} + +static int process_event(struct perf_evlist *evlist, union perf_event *event, + struct switch_tracking *switch_tracking) +{ + if (event->header.type == PERF_RECORD_SAMPLE) + return process_sample_event(evlist, event, switch_tracking); + + if (event->header.type == PERF_RECORD_COMM) { + int err, done = 0; + + err = check_comm(switch_tracking, event, "Test COMM 1", 0); + if (err < 0) + return -1; + done += err; + err = check_comm(switch_tracking, event, "Test COMM 2", 1); + if (err < 0) + return -1; + done += err; + err = check_comm(switch_tracking, event, "Test COMM 3", 2); + if (err < 0) + return -1; + done += err; + err = check_comm(switch_tracking, event, "Test COMM 4", 3); + if (err < 0) + return -1; + done += err; + if (done != 1) { + pr_debug("Unexpected comm event\n"); + return -1; + } + } + + return 0; +} + +struct event_node { + struct list_head list; + union perf_event *event; + u64 event_time; +}; + +static int add_event(struct perf_evlist *evlist, struct list_head *events, + union perf_event *event) +{ + struct perf_sample sample; + struct event_node *node; + + node = malloc(sizeof(struct event_node)); + if (!node) { + pr_debug("malloc failed\n"); + return -1; + } + node->event = event; + list_add(&node->list, events); + + if (perf_evlist__parse_sample(evlist, event, &sample)) { + pr_debug("perf_evlist__parse_sample failed\n"); + return -1; + } + + if (!sample.time) { + pr_debug("event with no time\n"); + return -1; + } + + node->event_time = sample.time; + + return 0; +} + +static void free_event_nodes(struct list_head *events) +{ + struct event_node *node; + + while (!list_empty(events)) { + node = list_entry(events->next, struct event_node, list); + list_del(&node->list); + free(node); + } +} + +static int compar(const void *a, const void *b) +{ + const struct event_node *nodea = a; + const struct event_node *nodeb = b; + s64 cmp = nodea->event_time - nodeb->event_time; + + return cmp; +} + +static int process_events(struct perf_evlist *evlist, + struct switch_tracking *switch_tracking) +{ + union perf_event *event; + unsigned pos, cnt = 0; + LIST_HEAD(events); + struct event_node *events_array, *node; + int i, ret; + + for (i = 0; i < evlist->nr_mmaps; i++) { + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + cnt += 1; + ret = add_event(evlist, &events, event); + perf_evlist__mmap_consume(evlist, i); + if (ret < 0) + goto out_free_nodes; + } + } + + events_array = calloc(cnt, sizeof(struct event_node)); + if (!events_array) { + pr_debug("calloc failed\n"); + ret = -1; + goto out_free_nodes; + } + + pos = 0; + list_for_each_entry(node, &events, list) + events_array[pos++] = *node; + + qsort(events_array, cnt, sizeof(struct event_node), compar); + + for (pos = 0; pos < cnt; pos++) { + ret = process_event(evlist, events_array[pos].event, + switch_tracking); + if (ret < 0) + goto out_free; + } + + ret = 0; +out_free: + pr_debug("%u events recorded\n", cnt); + free(events_array); +out_free_nodes: + free_event_nodes(&events); + return ret; +} + +/** + * test__switch_tracking - test using sched_switch and tracking events. + * + * This function implements a test that checks that sched_switch events and + * tracking events can be recorded for a workload (current process) using the + * evsel->system_wide and evsel->tracking flags (respectively) with other events + * sometimes enabled or disabled. + */ +int test__switch_tracking(void) +{ + const char *sched_switch = "sched:sched_switch"; + struct switch_tracking switch_tracking = { .tids = NULL, }; + struct record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .freq = 4000, + .target = { + .uses_mmap = true, + }, + }; + struct thread_map *threads = NULL; + struct cpu_map *cpus = NULL; + struct perf_evlist *evlist = NULL; + struct perf_evsel *evsel, *cpu_clocks_evsel, *cycles_evsel; + struct perf_evsel *switch_evsel, *tracking_evsel; + const char *comm; + int err = -1; + + threads = thread_map__new(-1, getpid(), UINT_MAX); + if (!threads) { + pr_debug("thread_map__new failed!\n"); + goto out_err; + } + + cpus = cpu_map__new(NULL); + if (!cpus) { + pr_debug("cpu_map__new failed!\n"); + goto out_err; + } + + evlist = perf_evlist__new(); + if (!evlist) { + pr_debug("perf_evlist__new failed!\n"); + goto out_err; + } + + perf_evlist__set_maps(evlist, cpus, threads); + + /* First event */ + err = parse_events(evlist, "cpu-clock:u"); + if (err) { + pr_debug("Failed to parse event dummy:u\n"); + goto out_err; + } + + cpu_clocks_evsel = perf_evlist__last(evlist); + + /* Second event */ + err = parse_events(evlist, "cycles:u"); + if (err) { + pr_debug("Failed to parse event cycles:u\n"); + goto out_err; + } + + cycles_evsel = perf_evlist__last(evlist); + + /* Third event */ + if (!perf_evlist__can_select_event(evlist, sched_switch)) { + fprintf(stderr, " (no sched_switch)"); + err = 0; + goto out; + } + + err = parse_events(evlist, sched_switch); + if (err) { + pr_debug("Failed to parse event %s\n", sched_switch); + goto out_err; + } + + switch_evsel = perf_evlist__last(evlist); + + perf_evsel__set_sample_bit(switch_evsel, CPU); + perf_evsel__set_sample_bit(switch_evsel, TIME); + + switch_evsel->system_wide = true; + switch_evsel->no_aux_samples = true; + switch_evsel->immediate = true; + + /* Test moving an event to the front */ + if (cycles_evsel == perf_evlist__first(evlist)) { + pr_debug("cycles event already at front"); + goto out_err; + } + perf_evlist__to_front(evlist, cycles_evsel); + if (cycles_evsel != perf_evlist__first(evlist)) { + pr_debug("Failed to move cycles event to front"); + goto out_err; + } + + perf_evsel__set_sample_bit(cycles_evsel, CPU); + perf_evsel__set_sample_bit(cycles_evsel, TIME); + + /* Fourth event */ + err = parse_events(evlist, "dummy:u"); + if (err) { + pr_debug("Failed to parse event dummy:u\n"); + goto out_err; + } + + tracking_evsel = perf_evlist__last(evlist); + + perf_evlist__set_tracking_event(evlist, tracking_evsel); + + tracking_evsel->attr.freq = 0; + tracking_evsel->attr.sample_period = 1; + + perf_evsel__set_sample_bit(tracking_evsel, TIME); + + /* Config events */ + perf_evlist__config(evlist, &opts); + + /* Check moved event is still at the front */ + if (cycles_evsel != perf_evlist__first(evlist)) { + pr_debug("Front event no longer at front"); + goto out_err; + } + + /* Check tracking event is tracking */ + if (!tracking_evsel->attr.mmap || !tracking_evsel->attr.comm) { + pr_debug("Tracking event not tracking\n"); + goto out_err; + } + + /* Check non-tracking events are not tracking */ + evlist__for_each(evlist, evsel) { + if (evsel != tracking_evsel) { + if (evsel->attr.mmap || evsel->attr.comm) { + pr_debug("Non-tracking event is tracking\n"); + goto out_err; + } + } + } + + if (perf_evlist__open(evlist) < 0) { + fprintf(stderr, " (not supported)"); + err = 0; + goto out; + } + + err = perf_evlist__mmap(evlist, UINT_MAX, false); + if (err) { + pr_debug("perf_evlist__mmap failed!\n"); + goto out_err; + } + + perf_evlist__enable(evlist); + + err = perf_evlist__disable_event(evlist, cpu_clocks_evsel); + if (err) { + pr_debug("perf_evlist__disable_event failed!\n"); + goto out_err; + } + + err = spin_sleep(); + if (err) { + pr_debug("spin_sleep failed!\n"); + goto out_err; + } + + comm = "Test COMM 1"; + err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); + if (err) { + pr_debug("PR_SET_NAME failed!\n"); + goto out_err; + } + + err = perf_evlist__disable_event(evlist, cycles_evsel); + if (err) { + pr_debug("perf_evlist__disable_event failed!\n"); + goto out_err; + } + + comm = "Test COMM 2"; + err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); + if (err) { + pr_debug("PR_SET_NAME failed!\n"); + goto out_err; + } + + err = spin_sleep(); + if (err) { + pr_debug("spin_sleep failed!\n"); + goto out_err; + } + + comm = "Test COMM 3"; + err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); + if (err) { + pr_debug("PR_SET_NAME failed!\n"); + goto out_err; + } + + err = perf_evlist__enable_event(evlist, cycles_evsel); + if (err) { + pr_debug("perf_evlist__disable_event failed!\n"); + goto out_err; + } + + comm = "Test COMM 4"; + err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); + if (err) { + pr_debug("PR_SET_NAME failed!\n"); + goto out_err; + } + + err = spin_sleep(); + if (err) { + pr_debug("spin_sleep failed!\n"); + goto out_err; + } + + perf_evlist__disable(evlist); + + switch_tracking.switch_evsel = switch_evsel; + switch_tracking.cycles_evsel = cycles_evsel; + + err = process_events(evlist, &switch_tracking); + + zfree(&switch_tracking.tids); + + if (err) + goto out_err; + + /* Check all 4 comm events were seen i.e. that evsel->tracking works */ + if (!switch_tracking.comm_seen[0] || !switch_tracking.comm_seen[1] || + !switch_tracking.comm_seen[2] || !switch_tracking.comm_seen[3]) { + pr_debug("Missing comm events\n"); + goto out_err; + } + + /* Check cycles event got enabled */ + if (!switch_tracking.cycles_before_comm_1) { + pr_debug("Missing cycles events\n"); + goto out_err; + } + + /* Check cycles event got disabled */ + if (switch_tracking.cycles_between_comm_2_and_comm_3) { + pr_debug("cycles events even though event was disabled\n"); + goto out_err; + } + + /* Check cycles event got enabled again */ + if (!switch_tracking.cycles_after_comm_4) { + pr_debug("Missing cycles events\n"); + goto out_err; + } +out: + if (evlist) { + perf_evlist__disable(evlist); + perf_evlist__delete(evlist); + } else { + cpu_map__delete(cpus); + thread_map__delete(threads); + } + + return err; + +out_err: + err = -1; + goto out; +} diff --git a/tools/perf/tests/task-exit.c b/tools/perf/tests/task-exit.c index 5ff3db318f12..3a8fedef83bc 100644 --- a/tools/perf/tests/task-exit.c +++ b/tools/perf/tests/task-exit.c @@ -42,6 +42,7 @@ int test__task_exit(void) .uses_mmap = true, }; const char *argv[] = { "true", NULL }; + char sbuf[STRERR_BUFSIZE]; signal(SIGCHLD, sig_handler); @@ -82,13 +83,14 @@ int test__task_exit(void) err = perf_evlist__open(evlist); if (err < 0) { - pr_debug("Couldn't open the evlist: %s\n", strerror(-err)); + pr_debug("Couldn't open the evlist: %s\n", + strerror_r(-err, sbuf, sizeof(sbuf))); goto out_delete_evlist; } if (perf_evlist__mmap(evlist, 128, true) < 0) { pr_debug("failed to mmap events: %d (%s)\n", errno, - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); goto out_delete_evlist; } @@ -103,7 +105,7 @@ retry: } if (!exited || !nr_exit) { - poll(evlist->pollfd, evlist->nr_fds, -1); + perf_evlist__poll(evlist, -1); goto retry; } diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index ed64790a395f..00e776a87a9c 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -48,6 +48,9 @@ int test__mmap_thread_lookup(void); int test__thread_mg_share(void); int test__hists_output(void); int test__hists_cumulate(void); +int test__switch_tracking(void); +int test__fdarray__filter(void); +int test__fdarray__add(void); #if defined(__x86_64__) || defined(__i386__) || defined(__arm__) #ifdef HAVE_DWARF_UNWIND_SUPPORT diff --git a/tools/perf/tests/thread-mg-share.c b/tools/perf/tests/thread-mg-share.c index 2b2e0dbe114f..b028499dd3cf 100644 --- a/tools/perf/tests/thread-mg-share.c +++ b/tools/perf/tests/thread-mg-share.c @@ -2,6 +2,7 @@ #include "machine.h" #include "thread.h" #include "map.h" +#include "debug.h" int test__thread_mg_share(void) { diff --git a/tools/perf/ui/browser.c b/tools/perf/ui/browser.c index 3ccf6e14f89b..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; } @@ -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 03d4d6295f10..92ae72113965 100644 --- a/tools/perf/ui/browser.h +++ b/tools/perf/ui/browser.h @@ -14,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 04a229aa5c0f..8f60a970404f 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -10,6 +10,7 @@ #include "../../util/pstack.h" #include "../../util/sort.h" #include "../../util/util.h" +#include "../../util/top.h" #include "../../arch/common.h" #include "../browser.h" @@ -26,6 +27,7 @@ struct hist_browser { struct map_symbol *selection; int print_seq; bool show_dso; + bool show_headers; float min_pcnt; u64 nr_non_filtered_entries; u64 nr_callchain_rows; @@ -33,8 +35,7 @@ struct hist_browser { 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 struct rb_node *hists__filter_entries(struct rb_node *nd, @@ -57,11 +58,42 @@ static u32 hist_browser__nr_entries(struct hist_browser *hb) return nr_entries + hb->nr_callchain_rows; } -static void hist_browser__refresh_dimensions(struct hist_browser *browser) +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) @@ -74,7 +106,7 @@ static void hist_browser__reset(struct hist_browser *browser) hist_browser__update_nr_entries(browser); browser->b.nr_entries = hist_browser__nr_entries(browser); - hist_browser__refresh_dimensions(browser); + hist_browser__refresh_dimensions(&browser->b); ui_browser__reset_index(&browser->b); } @@ -197,8 +229,10 @@ static void callchain_node__init_have_children(struct callchain_node *node) { struct callchain_list *chain; - list_for_each_entry(chain, &node->val, list) + if (!list_empty(&node->val)) { + chain = list_entry(node->val.prev, struct callchain_list, list); chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root); + } callchain_node__init_have_children_rb_tree(node); } @@ -346,7 +380,7 @@ static void ui_browser__warn_lost_events(struct ui_browser *browser) "Or reduce the sampling frequency."); } -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; @@ -356,8 +390,7 @@ static int hist_browser__run(struct hist_browser *browser, const char *ev_name, browser->b.entries = &browser->hists->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) @@ -384,7 +417,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; } @@ -393,10 +426,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); @@ -410,6 +443,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; @@ -440,26 +477,87 @@ static char *callchain_list__sym_name(struct callchain_list *cl, return bf; } +struct callchain_print_arg { + /* for hists browser */ + off_t row_offset; + bool is_current_entry; + + /* for file dump */ + FILE *fp; + int printed; +}; + +typedef void (*print_callchain_entry_fn)(struct hist_browser *browser, + struct callchain_list *chain, + const char *str, int offset, + unsigned short row, + struct callchain_print_arg *arg); + +static void hist_browser__show_callchain_entry(struct hist_browser *browser, + struct callchain_list *chain, + const char *str, int offset, + unsigned short row, + struct callchain_print_arg *arg) +{ + int color, width; + char folded_sign = callchain_list__folded(chain); + + color = HE_COLORSET_NORMAL; + width = browser->b.width - (offset + 2); + if (ui_browser__is_current_entry(&browser->b, row)) { + browser->selection = &chain->ms; + color = HE_COLORSET_SELECTED; + arg->is_current_entry = true; + } + + ui_browser__set_color(&browser->b, color); + hist_browser__gotorc(browser, row, 0); + slsmg_write_nstring(" ", offset); + slsmg_printf("%c ", folded_sign); + slsmg_write_nstring(str, width); +} + +static void hist_browser__fprintf_callchain_entry(struct hist_browser *b __maybe_unused, + struct callchain_list *chain, + const char *str, int offset, + unsigned short row __maybe_unused, + struct callchain_print_arg *arg) +{ + char folded_sign = callchain_list__folded(chain); + + arg->printed += fprintf(arg->fp, "%*s%c %s\n", offset, " ", + folded_sign, str); +} + +typedef bool (*check_output_full_fn)(struct hist_browser *browser, + unsigned short row); + +static bool hist_browser__check_output_full(struct hist_browser *browser, + unsigned short row) +{ + return browser->b.rows == row; +} + +static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_unused, + unsigned short row __maybe_unused) +{ + return false; +} + #define LEVEL_OFFSET_STEP 3 -static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browser, - struct callchain_node *chain_node, - u64 total, int level, - unsigned short row, - off_t *row_offset, - bool *is_current_entry) +static int hist_browser__show_callchain(struct hist_browser *browser, + struct rb_root *root, int level, + unsigned short row, u64 total, + print_callchain_entry_fn print, + struct callchain_print_arg *arg, + check_output_full_fn is_output_full) { struct rb_node *node; - int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; - u64 new_total, remaining; + int first_row = row, offset = level * LEVEL_OFFSET_STEP; + u64 new_total; - if (callchain_param.mode == CHAIN_GRAPH_REL) - new_total = chain_node->children_hit; - else - new_total = total; - - remaining = new_total; - node = rb_first(&chain_node->rb_root); + node = rb_first(root); while (node) { struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); struct rb_node *next = rb_next(node); @@ -469,30 +567,28 @@ static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browse int first = true; int extra_offset = 0; - remaining -= cumul; - list_for_each_entry(chain, &child->val, list) { char bf[1024], *alloc_str; const char *str; - int color; bool was_first = first; if (first) first = false; - else + else if (level > 1) extra_offset = LEVEL_OFFSET_STEP; folded_sign = callchain_list__folded(chain); - if (*row_offset != 0) { - --*row_offset; + if (arg->row_offset != 0) { + arg->row_offset--; goto do_next; } alloc_str = NULL; str = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso); - if (was_first) { - double percent = cumul * 100.0 / new_total; + + if (was_first && level > 1) { + double percent = cumul * 100.0 / total; if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) str = "Not enough memory!"; @@ -500,22 +596,11 @@ static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browse str = alloc_str; } - color = HE_COLORSET_NORMAL; - width = browser->b.width - (offset + extra_offset + 2); - if (ui_browser__is_current_entry(&browser->b, row)) { - browser->selection = &chain->ms; - color = HE_COLORSET_SELECTED; - *is_current_entry = true; - } + print(browser, chain, str, offset + extra_offset, row, arg); - ui_browser__set_color(&browser->b, color); - ui_browser__gotorc(&browser->b, 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 (is_output_full(browser, ++row)) goto out; do_next: if (folded_sign == '+') @@ -524,89 +609,21 @@ do_next: if (folded_sign == '-') { const int new_level = level + (extra_offset ? 2 : 1); - row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total, - new_level, row, row_offset, - is_current_entry); - } - if (row == browser->b.height) - goto out; - node = next; - } -out: - return row - first_row; -} - -static int hist_browser__show_callchain_node(struct hist_browser *browser, - struct callchain_node *node, - int level, unsigned short row, - off_t *row_offset, - bool *is_current_entry) -{ - struct callchain_list *chain; - int first_row = row, - offset = level * LEVEL_OFFSET_STEP, - width = browser->b.width - offset; - char folded_sign = ' '; - - list_for_each_entry(chain, &node->val, list) { - char bf[1024], *s; - int color; - - folded_sign = callchain_list__folded(chain); - if (*row_offset != 0) { - --*row_offset; - continue; - } + if (callchain_param.mode == CHAIN_GRAPH_REL) + new_total = child->children_hit; + else + new_total = total; - color = HE_COLORSET_NORMAL; - if (ui_browser__is_current_entry(&browser->b, row)) { - browser->selection = &chain->ms; - color = HE_COLORSET_SELECTED; - *is_current_entry = true; + row += hist_browser__show_callchain(browser, &child->rb_root, + new_level, row, new_total, + print, arg, is_output_full); } - - s = callchain_list__sym_name(chain, bf, sizeof(bf), - browser->show_dso); - ui_browser__gotorc(&browser->b, 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) - goto out; - } - - if (folded_sign == '-') - row += hist_browser__show_callchain_node_rb_tree(browser, node, - browser->hists->stats.total_period, - level + 1, row, - row_offset, - is_current_entry); -out: - return row - first_row; -} - -static int hist_browser__show_callchain(struct hist_browser *browser, - struct rb_root *chain, - int level, unsigned short row, - off_t *row_offset, - bool *is_current_entry) -{ - struct rb_node *nd; - int first_row = row; - - for (nd = rb_first(chain); nd; nd = rb_next(nd)) { - struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); - - row += hist_browser__show_callchain_node(browser, node, level, - row, row_offset, - is_current_entry); - if (row == browser->b.height) + if (is_output_full(browser, row)) break; + node = next; } - +out: return row - first_row; } @@ -619,17 +636,18 @@ struct hpp_arg { static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...) { struct hpp_arg *arg = hpp->ptr; - int ret; + int ret, len; va_list args; double percent; va_start(args, fmt); + len = va_arg(args, int); percent = va_arg(args, double); va_end(args); ui_browser__set_percent_color(arg->b, percent, arg->current_entry); - ret = scnprintf(hpp->buf, hpp->size, fmt, percent); + ret = scnprintf(hpp->buf, hpp->size, fmt, len, percent); slsmg_printf("%s", hpp->buf); advance_hpp(hpp, ret); @@ -643,12 +661,12 @@ static u64 __hpp_get_##_field(struct hist_entry *he) \ } \ \ static int \ -hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ +hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, \ struct hist_entry *he) \ { \ - return __hpp__fmt(hpp, he, __hpp_get_##_field, " %6.2f%%", \ - __hpp__slsmg_color_printf, true); \ + return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%", \ + __hpp__slsmg_color_printf, true); \ } #define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ @@ -658,18 +676,20 @@ static u64 __hpp_get_acc_##_field(struct hist_entry *he) \ } \ \ static int \ -hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ +hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, \ struct hist_entry *he) \ { \ if (!symbol_conf.cumulate_callchain) { \ - int ret = scnprintf(hpp->buf, hpp->size, "%8s", "N/A"); \ + int len = fmt->user_len ?: fmt->len; \ + int ret = scnprintf(hpp->buf, hpp->size, \ + "%*s", len, "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); \ + return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \ + " %*.2f%%", __hpp__slsmg_color_printf, true); \ } __HPP_COLOR_PERCENT_FN(overhead, period) @@ -733,7 +753,7 @@ 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 (perf_hpp__should_skip(fmt)) @@ -777,17 +797,72 @@ static int hist_browser__show_entry(struct hist_browser *browser, } else --row_offset; - if (folded_sign == '-' && row != browser->b.height) { - printed += hist_browser__show_callchain(browser, &entry->sorted_chain, - 1, row, &row_offset, - ¤t_entry); - if (current_entry) + if (folded_sign == '-' && row != browser->b.rows) { + u64 total = hists__total_period(entry->hists); + struct callchain_print_arg arg = { + .row_offset = row_offset, + .is_current_entry = current_entry, + }; + + printed += hist_browser__show_callchain(browser, + &entry->sorted_chain, 1, row, total, + hist_browser__show_callchain_entry, &arg, + hist_browser__check_output_full); + + if (arg.is_current_entry) browser->he_selection = entry; } 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; + + 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) { @@ -801,9 +876,15 @@ 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)) { @@ -818,11 +899,11 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser) 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, @@ -984,113 +1065,21 @@ do_offset: } } -static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser, - struct callchain_node *chain_node, - u64 total, int level, - FILE *fp) -{ - struct rb_node *node; - int offset = level * LEVEL_OFFSET_STEP; - u64 new_total, remaining; - int printed = 0; - - if (callchain_param.mode == CHAIN_GRAPH_REL) - new_total = chain_node->children_hit; - else - new_total = total; - - remaining = new_total; - node = rb_first(&chain_node->rb_root); - while (node) { - struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); - struct rb_node *next = rb_next(node); - u64 cumul = callchain_cumul_hits(child); - struct callchain_list *chain; - char folded_sign = ' '; - int first = true; - int extra_offset = 0; - - remaining -= cumul; - - list_for_each_entry(chain, &child->val, list) { - char bf[1024], *alloc_str; - const char *str; - bool was_first = first; - - if (first) - first = false; - else - extra_offset = LEVEL_OFFSET_STEP; - - folded_sign = callchain_list__folded(chain); - - alloc_str = NULL; - str = callchain_list__sym_name(chain, bf, sizeof(bf), - browser->show_dso); - if (was_first) { - double percent = cumul * 100.0 / new_total; - - if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) - str = "Not enough memory!"; - else - str = alloc_str; - } - - printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str); - free(alloc_str); - if (folded_sign == '+') - break; - } - - if (folded_sign == '-') { - const int new_level = level + (extra_offset ? 2 : 1); - printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total, - new_level, fp); - } - - node = next; - } - - return printed; -} - -static int hist_browser__fprintf_callchain_node(struct hist_browser *browser, - struct callchain_node *node, - int level, FILE *fp) -{ - struct callchain_list *chain; - int offset = level * LEVEL_OFFSET_STEP; - char folded_sign = ' '; - int printed = 0; - - list_for_each_entry(chain, &node->val, list) { - char bf[1024], *s; - - folded_sign = callchain_list__folded(chain); - s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso); - printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s); - } - - if (folded_sign == '-') - printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node, - browser->hists->stats.total_period, - level + 1, fp); - return printed; -} - static int hist_browser__fprintf_callchain(struct hist_browser *browser, - struct rb_root *chain, int level, FILE *fp) + struct hist_entry *he, FILE *fp) { - struct rb_node *nd; - int printed = 0; - - for (nd = rb_first(chain); nd; nd = rb_next(nd)) { - struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + u64 total = hists__total_period(he->hists); + struct callchain_print_arg arg = { + .fp = fp, + }; - printed += hist_browser__fprintf_callchain_node(browser, node, level, fp); - } + if (symbol_conf.cumulate_callchain) + total = he->stat_acc->period; - return printed; + hist_browser__show_callchain(browser, &he->sorted_chain, 1, 0, total, + hist_browser__fprintf_callchain_entry, &arg, + hist_browser__check_dump_full); + return arg.printed; } static int hist_browser__fprintf_entry(struct hist_browser *browser, @@ -1129,7 +1118,7 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser, printed += fprintf(fp, "%s\n", rtrim(s)); if (folded_sign == '-') - printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp); + printed += hist_browser__fprintf_callchain(browser, he, fp); return printed; } @@ -1191,8 +1180,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; @@ -1213,8 +1204,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; @@ -1223,6 +1213,7 @@ 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); @@ -1390,7 +1381,7 @@ static void hist_browser__update_nr_entries(struct hist_browser *hb) } 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, @@ -1406,6 +1397,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, char buf[64]; char script_opt[64]; int delay_secs = hbt ? hbt->refresh : 0; + struct perf_hpp_fmt *fmt; #define HIST_BROWSER_HELP_COMMON \ "h/?/F1 Show this window\n" \ @@ -1422,6 +1414,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, "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 @@ -1436,6 +1429,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, "P Print histograms to perf.hist.N\n" "t Zoom into current Thread\n" "V Verbose (DSO names in callchains, etc)\n" + "z Toggle zeroing of samples\n" "/ Filter symbol by name"; if (browser == NULL) @@ -1454,6 +1448,12 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, memset(options, 0, sizeof(options)); + perf_hpp__for_each_format(fmt) + perf_hpp__reset_width(fmt, hists); + + if (symbol_conf.col_width_list_str) + perf_hpp__set_user_width(symbol_conf.col_width_list_str); + while (1) { const struct thread *thread = NULL; const struct dso *dso = NULL; @@ -1465,7 +1465,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); @@ -1530,6 +1530,13 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, case 'F': symbol_conf.filter_relative ^= 1; continue; + case 'z': + if (!is_report_browser(hbt)) { + struct perf_top *top = hbt->arg; + + top->zero = !top->zero; + } + continue; case K_F1: case 'h': case '?': @@ -1843,7 +1850,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; @@ -1876,9 +1883,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); @@ -1982,10 +1988,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 6ca60e482cdc..f3fa4258b256 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -11,6 +11,7 @@ static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...) { int ret = 0; + int len; va_list args; double percent; const char *markup; @@ -18,6 +19,7 @@ static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...) size_t size = hpp->size; va_start(args, fmt); + len = va_arg(args, int); percent = va_arg(args, double); va_end(args); @@ -25,7 +27,7 @@ static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...) if (markup) ret += scnprintf(buf, size, markup); - ret += scnprintf(buf + ret, size - ret, fmt, percent); + ret += scnprintf(buf + ret, size - ret, fmt, len, percent); if (markup) ret += scnprintf(buf + ret, size - ret, "</span>"); @@ -39,12 +41,12 @@ static u64 he_get_##_field(struct hist_entry *he) \ return he->stat._field; \ } \ \ -static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ +static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, \ struct hist_entry *he) \ { \ - return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \ - __percent_color_snprintf, true); \ + return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ + __percent_color_snprintf, true); \ } #define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ @@ -57,8 +59,8 @@ 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); \ + return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \ + __percent_color_snprintf, true); \ } __HPP_COLOR_PERCENT_FN(overhead, period) @@ -205,10 +207,8 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, 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), - -1, ltrim(s), + -1, fmt->name, renderer, "markup", col_idx++, NULL); } diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c index 498adb23c02e..2af18376b077 100644 --- a/tools/perf/ui/hist.c +++ b/tools/perf/ui/hist.c @@ -15,9 +15,9 @@ __ret; \ }) -int __hpp__fmt(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 int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, + hpp_field_fn get_field, const char *fmt, int len, + hpp_snprint_fn print_fn, bool fmt_percent) { int ret; struct hists *hists = he->hists; @@ -32,9 +32,9 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, 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, len, percent); } else - ret = hpp__call_print_fn(hpp, print_fn, fmt, get_field(he)); + ret = hpp__call_print_fn(hpp, print_fn, fmt, len, get_field(he)); if (perf_evsel__is_group_event(evsel)) { int prev_idx, idx_delta; @@ -60,19 +60,19 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, */ if (fmt_percent) { ret += hpp__call_print_fn(hpp, print_fn, - fmt, 0.0); + fmt, len, 0.0); } else { ret += hpp__call_print_fn(hpp, print_fn, - fmt, 0ULL); + fmt, len, 0ULL); } } if (fmt_percent) { - ret += hpp__call_print_fn(hpp, print_fn, fmt, + ret += hpp__call_print_fn(hpp, print_fn, fmt, len, 100.0 * period / total); } else { ret += hpp__call_print_fn(hpp, print_fn, fmt, - period); + len, period); } prev_idx = perf_evsel__group_idx(evsel); @@ -86,10 +86,10 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, */ if (fmt_percent) { ret += hpp__call_print_fn(hpp, print_fn, - fmt, 0.0); + fmt, len, 0.0); } else { ret += hpp__call_print_fn(hpp, print_fn, - fmt, 0ULL); + fmt, len, 0ULL); } } } @@ -104,16 +104,35 @@ 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) +int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he, hpp_field_fn get_field, + const char *fmtstr, hpp_snprint_fn print_fn, bool fmt_percent) +{ + int len = fmt->user_len ?: fmt->len; + + if (symbol_conf.field_sep) { + return __hpp__fmt(hpp, he, get_field, fmtstr, 1, + print_fn, fmt_percent); + } + + if (fmt_percent) + len -= 2; /* 2 for a space and a % sign */ + else + len -= 1; + + return __hpp__fmt(hpp, he, get_field, fmtstr, len, print_fn, fmt_percent); +} + +int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he, hpp_field_fn get_field, + const char *fmtstr, 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"); + int len = fmt->user_len ?: fmt->len; + return snprintf(hpp->buf, hpp->size, " %*s", len - 1, "N/A"); } - return __hpp__fmt(hpp, he, get_field, fmt, print_fn, fmt_percent); + return hpp__fmt(fmt, hpp, he, get_field, fmtstr, print_fn, fmt_percent); } static int field_cmp(u64 field_a, u64 field_b) @@ -190,30 +209,26 @@ static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b, 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, \ - struct perf_evsel *evsel) \ -{ \ - int len = _min_width; \ - \ - if (symbol_conf.event_group) \ - len = max(len, evsel->nr_members * _unit_width); \ - \ - return scnprintf(hpp->buf, hpp->size, "%*s", len, _str); \ -} - -#define __HPP_WIDTH_FN(_type, _min_width, _unit_width) \ -static int hpp__width_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ - struct perf_hpp *hpp __maybe_unused, \ - struct perf_evsel *evsel) \ -{ \ - int len = _min_width; \ - \ - if (symbol_conf.event_group) \ - len = max(len, evsel->nr_members * _unit_width); \ - \ - return len; \ +static int hpp__width_fn(struct perf_hpp_fmt *fmt, + struct perf_hpp *hpp __maybe_unused, + struct perf_evsel *evsel) +{ + int len = fmt->user_len ?: fmt->len; + + if (symbol_conf.event_group) + len = max(len, evsel->nr_members * fmt->len); + + if (len < (int)strlen(fmt->name)) + len = strlen(fmt->name); + + return len; +} + +static int hpp__header_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct perf_evsel *evsel) +{ + int len = hpp__width_fn(fmt, hpp, evsel); + return scnprintf(hpp->buf, hpp->size, "%*s", len, fmt->name); } static int hpp_color_scnprintf(struct perf_hpp *hpp, const char *fmt, ...) @@ -221,11 +236,12 @@ static int hpp_color_scnprintf(struct perf_hpp *hpp, const char *fmt, ...) va_list args; ssize_t ssize = hpp->size; double percent; - int ret; + int ret, len; va_start(args, fmt); + len = va_arg(args, int); percent = va_arg(args, double); - ret = value_color_snprintf(hpp->buf, hpp->size, fmt, percent); + ret = percent_color_len_snprintf(hpp->buf, hpp->size, fmt, len, percent); va_end(args); return (ret >= ssize) ? (ssize - 1) : ret; @@ -250,20 +266,19 @@ static u64 he_get_##_field(struct hist_entry *he) \ return he->stat._field; \ } \ \ -static int hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ +static int hpp__color_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ - return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \ - hpp_color_scnprintf, true); \ + return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ + hpp_color_scnprintf, true); \ } #define __HPP_ENTRY_PERCENT_FN(_type, _field) \ -static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \ +static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ 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, fmt, \ - hpp_entry_scnprintf, true); \ + return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ + hpp_entry_scnprintf, true); \ } #define __HPP_SORT_FN(_type, _field) \ @@ -278,20 +293,19 @@ 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, \ +static int hpp__color_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ - return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%", \ - hpp_color_scnprintf, true); \ + return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.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, \ +static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ 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); \ + return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \ + hpp_entry_scnprintf, true); \ } #define __HPP_SORT_ACC_FN(_type, _field) \ @@ -306,12 +320,11 @@ static u64 he_get_raw_##_field(struct hist_entry *he) \ return he->stat._field; \ } \ \ -static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \ +static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ 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, fmt, \ - hpp_entry_scnprintf, false); \ + return hpp__fmt(fmt, hpp, he, he_get_raw_##_field, " %*"PRIu64, \ + hpp_entry_scnprintf, false); \ } #define __HPP_SORT_RAW_FN(_type, _field) \ @@ -321,37 +334,29 @@ static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \ } -#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) \ +#define HPP_PERCENT_FNS(_type, _field) \ __HPP_COLOR_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) \ +#define HPP_PERCENT_ACC_FNS(_type, _field) \ __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) \ +#define HPP_RAW_FNS(_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_PERCENT_FNS(overhead, period) +HPP_PERCENT_FNS(overhead_sys, period_sys) +HPP_PERCENT_FNS(overhead_us, period_us) +HPP_PERCENT_FNS(overhead_guest_sys, period_guest_sys) +HPP_PERCENT_FNS(overhead_guest_us, period_guest_us) +HPP_PERCENT_ACC_FNS(overhead_acc, period) -HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12) -HPP_RAW_FNS(period, "Period", period, 12, 12) +HPP_RAW_FNS(samples, nr_events) +HPP_RAW_FNS(period, period) static int64_t hpp__nop_cmp(struct hist_entry *a __maybe_unused, struct hist_entry *b __maybe_unused) @@ -359,47 +364,50 @@ static int64_t hpp__nop_cmp(struct hist_entry *a __maybe_unused, return 0; } -#define HPP__COLOR_PRINT_FNS(_name) \ +#define HPP__COLOR_PRINT_FNS(_name, _fn) \ { \ - .header = hpp__header_ ## _name, \ - .width = hpp__width_ ## _name, \ - .color = hpp__color_ ## _name, \ - .entry = hpp__entry_ ## _name, \ + .name = _name, \ + .header = hpp__header_fn, \ + .width = hpp__width_fn, \ + .color = hpp__color_ ## _fn, \ + .entry = hpp__entry_ ## _fn, \ .cmp = hpp__nop_cmp, \ .collapse = hpp__nop_cmp, \ - .sort = hpp__sort_ ## _name, \ + .sort = hpp__sort_ ## _fn, \ } -#define HPP__COLOR_ACC_PRINT_FNS(_name) \ +#define HPP__COLOR_ACC_PRINT_FNS(_name, _fn) \ { \ - .header = hpp__header_ ## _name, \ - .width = hpp__width_ ## _name, \ - .color = hpp__color_ ## _name, \ - .entry = hpp__entry_ ## _name, \ + .name = _name, \ + .header = hpp__header_fn, \ + .width = hpp__width_fn, \ + .color = hpp__color_ ## _fn, \ + .entry = hpp__entry_ ## _fn, \ .cmp = hpp__nop_cmp, \ .collapse = hpp__nop_cmp, \ - .sort = hpp__sort_ ## _name, \ + .sort = hpp__sort_ ## _fn, \ } -#define HPP__PRINT_FNS(_name) \ +#define HPP__PRINT_FNS(_name, _fn) \ { \ - .header = hpp__header_ ## _name, \ - .width = hpp__width_ ## _name, \ - .entry = hpp__entry_ ## _name, \ + .name = _name, \ + .header = hpp__header_fn, \ + .width = hpp__width_fn, \ + .entry = hpp__entry_ ## _fn, \ .cmp = hpp__nop_cmp, \ .collapse = hpp__nop_cmp, \ - .sort = hpp__sort_ ## _name, \ + .sort = hpp__sort_ ## _fn, \ } struct perf_hpp_fmt perf_hpp__format[] = { - HPP__COLOR_PRINT_FNS(overhead), - HPP__COLOR_PRINT_FNS(overhead_sys), - 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) + HPP__COLOR_PRINT_FNS("Overhead", overhead), + HPP__COLOR_PRINT_FNS("sys", overhead_sys), + HPP__COLOR_PRINT_FNS("usr", overhead_us), + HPP__COLOR_PRINT_FNS("guest sys", overhead_guest_sys), + HPP__COLOR_PRINT_FNS("guest usr", overhead_guest_us), + HPP__COLOR_ACC_PRINT_FNS("Children", overhead_acc), + HPP__PRINT_FNS("Samples", samples), + HPP__PRINT_FNS("Period", period) }; LIST_HEAD(perf_hpp__list); @@ -444,14 +452,12 @@ void perf_hpp__init(void) /* * If user specified field order, no need to setup default fields. */ - if (field_order) + if (is_strict_order(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__format[PERF_HPP__OVERHEAD].name = "Self"; } perf_hpp__column_enable(PERF_HPP__OVERHEAD); @@ -513,11 +519,11 @@ void perf_hpp__column_disable(unsigned col) void perf_hpp__cancel_cumulate(void) { - if (field_order) + if (is_strict_order(field_order)) return; perf_hpp__column_disable(PERF_HPP__OVERHEAD_ACC); - perf_hpp__format[PERF_HPP__OVERHEAD].header = hpp__header_overhead; + perf_hpp__format[PERF_HPP__OVERHEAD].name = "Overhead"; } void perf_hpp__setup_output_field(void) @@ -622,3 +628,59 @@ unsigned int hists__sort_list_width(struct hists *hists) return ret; } + +void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists) +{ + int idx; + + if (perf_hpp__is_sort_entry(fmt)) + return perf_hpp__reset_sort_width(fmt, hists); + + for (idx = 0; idx < PERF_HPP__MAX_INDEX; idx++) { + if (fmt == &perf_hpp__format[idx]) + break; + } + + if (idx == PERF_HPP__MAX_INDEX) + return; + + switch (idx) { + case PERF_HPP__OVERHEAD: + case PERF_HPP__OVERHEAD_SYS: + case PERF_HPP__OVERHEAD_US: + case PERF_HPP__OVERHEAD_ACC: + fmt->len = 8; + break; + + case PERF_HPP__OVERHEAD_GUEST_SYS: + case PERF_HPP__OVERHEAD_GUEST_US: + fmt->len = 9; + break; + + case PERF_HPP__SAMPLES: + case PERF_HPP__PERIOD: + fmt->len = 12; + break; + + default: + break; + } +} + +void perf_hpp__set_user_width(const char *width_list_str) +{ + struct perf_hpp_fmt *fmt; + const char *ptr = width_list_str; + + perf_hpp__for_each_format(fmt) { + char *p; + + int len = strtol(ptr, &p, 10); + fmt->user_len = len; + + if (*p == ',') + ptr = p + 1; + else + break; + } +} diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index 90122abd3721..15b451acbde6 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -395,10 +395,12 @@ 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 (symbol_conf.col_width_list_str) + perf_hpp__set_user_width(symbol_conf.col_width_list_str); + if (!show_header) goto print_entries; @@ -479,7 +481,7 @@ print_entries: if (h->ms.map == NULL && verbose > 1) { __map_groups__fprintf_maps(h->thread->mg, - MAP__FUNCTION, verbose, fp); + MAP__FUNCTION, fp); fprintf(fp, "%.10s end\n", graph_dotted_line); } } diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index 809b4c50beae..36437527dbb3 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -232,9 +232,16 @@ static int mov__parse(struct ins_operands *ops) return -1; target = ++s; + comment = strchr(s, '#'); - while (s[0] != '\0' && !isspace(s[0])) - ++s; + if (comment != NULL) + s = comment - 1; + else + s = strchr(s, '\0') - 1; + + while (s > target && isspace(s[0])) + --s; + s++; prev = *s; *s = '\0'; @@ -244,7 +251,6 @@ static int mov__parse(struct ins_operands *ops) if (ops->target.raw == NULL) goto out_free_source; - comment = strchr(s, '#'); if (comment == NULL) return 0; @@ -899,10 +905,8 @@ int symbol__annotate(struct symbol *sym, struct map *map, size_t privsize) struct kcore_extract kce; bool delete_extract = false; - if (filename) { - snprintf(symfs_filename, sizeof(symfs_filename), "%s%s", - symbol_conf.symfs, filename); - } + if (filename) + symbol__join_symfs(symfs_filename, filename); if (filename == NULL) { if (dso->has_build_id) { @@ -922,8 +926,7 @@ fallback: * DSO is the same as when 'perf record' ran. */ filename = (char *)dso->long_name; - snprintf(symfs_filename, sizeof(symfs_filename), "%s%s", - symbol_conf.symfs, filename); + symbol__join_symfs(symfs_filename, filename); free_filename = false; } diff --git a/tools/perf/util/cache.h b/tools/perf/util/cache.h index 7b176dd02e1a..5cf9e1b5989d 100644 --- a/tools/perf/util/cache.h +++ b/tools/perf/util/cache.h @@ -22,6 +22,7 @@ typedef int (*config_fn_t)(const char *, const char *, void *); extern int perf_default_config(const char *, const char *, void *); extern int perf_config(config_fn_t fn, void *); extern int perf_config_int(const char *, const char *); +extern u64 perf_config_u64(const char *, const char *); extern int perf_config_bool(const char *, const char *); extern int config_error_nonbool(const char *); extern const char *perf_config_dirname(const char *, const char *); diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 48b6d3f50012..c84d3f8dcb75 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -25,77 +25,172 @@ __thread struct callchain_cursor callchain_cursor; -int -parse_callchain_report_opt(const char *arg) +#ifdef HAVE_DWARF_UNWIND_SUPPORT +static int get_stack_size(const char *str, unsigned long *_size) { - char *tok, *tok2; char *endptr; + unsigned long size; + unsigned long max_size = round_down(USHRT_MAX, sizeof(u64)); - symbol_conf.use_callchain = true; + size = strtoul(str, &endptr, 0); - if (!arg) + do { + if (*endptr) + break; + + size = round_up(size, sizeof(u64)); + if (!size || size > max_size) + break; + + *_size = size; return 0; - tok = strtok((char *)arg, ","); - if (!tok) - return -1; + } while (0); - /* get the output mode */ - if (!strncmp(tok, "graph", strlen(arg))) { - callchain_param.mode = CHAIN_GRAPH_ABS; + pr_err("callchain: Incorrect stack dump size (max %ld): %s\n", + max_size, str); + return -1; +} +#endif /* HAVE_DWARF_UNWIND_SUPPORT */ - } 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; - } +int parse_callchain_record_opt(const char *arg) +{ + char *tok, *name, *saveptr = NULL; + char *buf; + int ret = -1; + + /* We need buffer that we know we can write to. */ + buf = malloc(strlen(arg) + 1); + if (!buf) + return -ENOMEM; + + strcpy(buf, arg); + + tok = strtok_r((char *)buf, ",", &saveptr); + name = tok ? : (char *)buf; + + do { + /* Framepointer style */ + if (!strncmp(name, "fp", sizeof("fp"))) { + if (!strtok_r(NULL, ",", &saveptr)) { + callchain_param.record_mode = CALLCHAIN_FP; + ret = 0; + } else + pr_err("callchain: No more arguments " + "needed for -g fp\n"); + break; - /* get the min percentage */ - tok = strtok(NULL, ","); - if (!tok) - goto setup; +#ifdef HAVE_DWARF_UNWIND_SUPPORT + /* Dwarf style */ + } else if (!strncmp(name, "dwarf", sizeof("dwarf"))) { + const unsigned long default_stack_dump_size = 8192; - callchain_param.min_percent = strtod(tok, &endptr); - if (tok == endptr) - return -1; + ret = 0; + callchain_param.record_mode = CALLCHAIN_DWARF; + callchain_param.dump_size = default_stack_dump_size; - /* get the print limit */ - tok2 = strtok(NULL, ","); - if (!tok2) - goto setup; + tok = strtok_r(NULL, ",", &saveptr); + if (tok) { + unsigned long size = 0; - if (tok2[0] != 'c') { - callchain_param.print_limit = strtoul(tok2, &endptr, 0); - tok2 = strtok(NULL, ","); - if (!tok2) - goto setup; + ret = get_stack_size(tok, &size); + callchain_param.dump_size = size; + } +#endif /* HAVE_DWARF_UNWIND_SUPPORT */ + } else { + pr_err("callchain: Unknown --call-graph option " + "value: %s\n", arg); + break; + } + + } while (0); + + free(buf); + return ret; +} + +static int parse_callchain_mode(const char *value) +{ + if (!strncmp(value, "graph", strlen(value))) { + callchain_param.mode = CHAIN_GRAPH_ABS; + return 0; + } + if (!strncmp(value, "flat", strlen(value))) { + callchain_param.mode = CHAIN_FLAT; + return 0; } + if (!strncmp(value, "fractal", strlen(value))) { + callchain_param.mode = CHAIN_GRAPH_REL; + return 0; + } + return -1; +} - /* get the call chain order */ - if (!strncmp(tok2, "caller", strlen("caller"))) +static int parse_callchain_order(const char *value) +{ + if (!strncmp(value, "caller", strlen(value))) { callchain_param.order = ORDER_CALLER; - else if (!strncmp(tok2, "callee", strlen("callee"))) + return 0; + } + if (!strncmp(value, "callee", strlen(value))) { callchain_param.order = ORDER_CALLEE; - else - return -1; + return 0; + } + return -1; +} - /* Get the sort key */ - tok2 = strtok(NULL, ","); - if (!tok2) - goto setup; - if (!strncmp(tok2, "function", strlen("function"))) +static int parse_callchain_sort_key(const char *value) +{ + if (!strncmp(value, "function", strlen(value))) { callchain_param.key = CCKEY_FUNCTION; - else if (!strncmp(tok2, "address", strlen("address"))) + return 0; + } + if (!strncmp(value, "address", strlen(value))) { callchain_param.key = CCKEY_ADDRESS; - else - return -1; -setup: + return 0; + } + return -1; +} + +int +parse_callchain_report_opt(const char *arg) +{ + char *tok; + char *endptr; + bool minpcnt_set = false; + + symbol_conf.use_callchain = true; + + if (!arg) + return 0; + + while ((tok = strtok((char *)arg, ",")) != NULL) { + if (!strncmp(tok, "none", strlen(tok))) { + callchain_param.mode = CHAIN_NONE; + symbol_conf.use_callchain = false; + return 0; + } + + if (!parse_callchain_mode(tok) || + !parse_callchain_order(tok) || + !parse_callchain_sort_key(tok)) { + /* parsing ok - move on to the next */ + } else if (!minpcnt_set) { + /* try to get the min percent */ + callchain_param.min_percent = strtod(tok, &endptr); + if (tok == endptr) + return -1; + minpcnt_set = true; + } else { + /* try print limit at last */ + callchain_param.print_limit = strtoul(tok, &endptr, 0); + if (tok == endptr) + return -1; + } + + arg = NULL; + } + if (callchain_register_param(&callchain_param) < 0) { pr_err("Can't register callchain params\n"); return -1; @@ -103,6 +198,47 @@ setup: return 0; } +int perf_callchain_config(const char *var, const char *value) +{ + char *endptr; + + if (prefixcmp(var, "call-graph.")) + return 0; + var += sizeof("call-graph.") - 1; + + if (!strcmp(var, "record-mode")) + return parse_callchain_record_opt(value); +#ifdef HAVE_DWARF_UNWIND_SUPPORT + if (!strcmp(var, "dump-size")) { + unsigned long size = 0; + int ret; + + ret = get_stack_size(value, &size); + callchain_param.dump_size = size; + + return ret; + } +#endif + if (!strcmp(var, "print-type")) + return parse_callchain_mode(value); + if (!strcmp(var, "order")) + return parse_callchain_order(value); + if (!strcmp(var, "sort-key")) + return parse_callchain_sort_key(value); + if (!strcmp(var, "threshold")) { + callchain_param.min_percent = strtod(value, &endptr); + if (value == endptr) + return -1; + } + if (!strcmp(var, "print-limit")) { + callchain_param.print_limit = strtod(value, &endptr); + if (value == endptr) + return -1; + } + + return 0; +} + static void rb_insert_callchain(struct rb_root *root, struct callchain_node *chain, enum chain_mode mode) @@ -626,7 +762,7 @@ 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); } diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index 8f84423a75da..2a1f5a46543a 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -54,6 +54,9 @@ enum chain_key { }; struct callchain_param { + bool enabled; + enum perf_call_graph_mode record_mode; + u32 dump_size; enum chain_mode mode; u32 print_limit; double min_percent; @@ -154,7 +157,6 @@ static inline void callchain_cursor_advance(struct callchain_cursor *cursor) struct option; struct hist_entry; -int record_parse_callchain(const char *arg, struct record_opts *opts); int record_parse_callchain_opt(const struct option *opt, const char *arg, int unset); int record_callchain_opt(const struct option *opt, const char *arg, int unset); @@ -166,7 +168,9 @@ int fill_callchain_info(struct addr_location *al, struct callchain_cursor_node * bool hide_unresolved); extern const char record_callchain_help[]; +int parse_callchain_record_opt(const char *arg); int parse_callchain_report_opt(const char *arg); +int perf_callchain_config(const char *var, const char *value); static inline void callchain_cursor_snapshot(struct callchain_cursor *dest, struct callchain_cursor *src) @@ -176,4 +180,17 @@ static inline void callchain_cursor_snapshot(struct callchain_cursor *dest, 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..47b78b3f0325 --- /dev/null +++ b/tools/perf/util/cloexec.c @@ -0,0 +1,74 @@ +#include <sched.h> +#include "util.h" +#include "../perf.h" +#include "cloexec.h" +#include "asm/bug.h" +#include "debug.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_TYPE_SOFTWARE, + .config = PERF_COUNT_SW_CPU_CLOCK, + .exclude_kernel = 1, + }; + int fd; + int err; + int cpu; + pid_t pid = -1; + char sbuf[STRERR_BUFSIZE]; + + cpu = sched_getcpu(); + if (cpu < 0) + cpu = 0; + + while (1) { + /* check cloexec flag */ + fd = sys_perf_event_open(&attr, pid, cpu, -1, + PERF_FLAG_FD_CLOEXEC); + if (fd < 0 && pid == -1 && errno == EACCES) { + pid = 0; + continue; + } + break; + } + err = errno; + + if (fd >= 0) { + close(fd); + return 1; + } + + WARN_ONCE(err != EINVAL && err != EBUSY, + "perf_event_open(..., PERF_FLAG_FD_CLOEXEC) failed with unexpected error %d (%s)\n", + err, strerror_r(err, sbuf, sizeof(sbuf))); + + /* not supported, confirm error related to PERF_FLAG_FD_CLOEXEC */ + fd = sys_perf_event_open(&attr, pid, cpu, -1, 0); + err = errno; + + if (WARN_ONCE(fd < 0 && err != EBUSY, + "perf_event_open(..., 0) failed unexpectedly with error %d (%s)\n", + err, strerror_r(err, sbuf, sizeof(sbuf)))) + 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/color.c b/tools/perf/util/color.c index 87b8672eb413..f4654183d391 100644 --- a/tools/perf/util/color.c +++ b/tools/perf/util/color.c @@ -335,3 +335,19 @@ int percent_color_snprintf(char *bf, size_t size, const char *fmt, ...) va_end(args); return value_color_snprintf(bf, size, fmt, percent); } + +int percent_color_len_snprintf(char *bf, size_t size, const char *fmt, ...) +{ + va_list args; + int len; + double percent; + const char *color; + + va_start(args, fmt); + len = va_arg(args, int); + percent = va_arg(args, double); + va_end(args); + + color = get_percent_color(percent); + return color_snprintf(bf, size, color, fmt, len, percent); +} diff --git a/tools/perf/util/color.h b/tools/perf/util/color.h index 7ff30a62a132..0a594b8a0c26 100644 --- a/tools/perf/util/color.h +++ b/tools/perf/util/color.h @@ -41,6 +41,7 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf); int value_color_snprintf(char *bf, size_t size, const char *fmt, double value); int percent_color_snprintf(char *bf, size_t size, const char *fmt, ...); +int percent_color_len_snprintf(char *bf, size_t size, const char *fmt, ...); int percent_color_fprintf(FILE *fp, const char *fmt, double percent); const char *get_percent_color(double percent); diff --git a/tools/perf/util/comm.c b/tools/perf/util/comm.c index f9e777629e21..b2bb59df65e1 100644 --- a/tools/perf/util/comm.c +++ b/tools/perf/util/comm.c @@ -74,7 +74,7 @@ static struct comm_str *comm_str__findnew(const char *str, struct rb_root *root) return new; } -struct comm *comm__new(const char *str, u64 timestamp) +struct comm *comm__new(const char *str, u64 timestamp, bool exec) { struct comm *comm = zalloc(sizeof(*comm)); @@ -82,6 +82,7 @@ struct comm *comm__new(const char *str, u64 timestamp) return NULL; comm->start = timestamp; + comm->exec = exec; comm->comm_str = comm_str__findnew(str, &comm_str_root); if (!comm->comm_str) { @@ -94,7 +95,7 @@ struct comm *comm__new(const char *str, u64 timestamp) return comm; } -int comm__override(struct comm *comm, const char *str, u64 timestamp) +int comm__override(struct comm *comm, const char *str, u64 timestamp, bool exec) { struct comm_str *new, *old = comm->comm_str; @@ -106,6 +107,8 @@ int comm__override(struct comm *comm, const char *str, u64 timestamp) comm_str__put(old); comm->comm_str = new; comm->start = timestamp; + if (exec) + comm->exec = true; return 0; } diff --git a/tools/perf/util/comm.h b/tools/perf/util/comm.h index fac5bd51befc..51c10ab257f8 100644 --- a/tools/perf/util/comm.h +++ b/tools/perf/util/comm.h @@ -11,11 +11,13 @@ struct comm { struct comm_str *comm_str; u64 start; struct list_head list; + bool exec; }; void comm__free(struct comm *comm); -struct comm *comm__new(const char *str, u64 timestamp); +struct comm *comm__new(const char *str, u64 timestamp, bool exec); const char *comm__str(const struct comm *comm); -int comm__override(struct comm *comm, const char *str, u64 timestamp); +int comm__override(struct comm *comm, const char *str, u64 timestamp, + bool exec); #endif /* __PERF_COMM_H */ diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index 24519e14ac56..57ff826f150b 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -222,7 +222,8 @@ static int perf_parse_file(config_fn_t fn, void *data) const unsigned char *bomptr = utf8_bom; for (;;) { - int c = get_next_char(); + int line, c = get_next_char(); + if (bomptr && *bomptr) { /* We are at the file beginning; skip UTF8-encoded BOM * if present. Sane editors won't put this in on their @@ -261,8 +262,16 @@ static int perf_parse_file(config_fn_t fn, void *data) if (!isalpha(c)) break; var[baselen] = tolower(c); - if (get_value(fn, data, var, baselen+1) < 0) + + /* + * The get_value function might or might not reach the '\n', + * so saving the current line number for error reporting. + */ + line = config_linenr; + if (get_value(fn, data, var, baselen+1) < 0) { + config_linenr = line; break; + } } die("bad config file line %d in %s", config_linenr, config_file_name); } @@ -286,6 +295,21 @@ static int parse_unit_factor(const char *end, unsigned long *val) return 0; } +static int perf_parse_llong(const char *value, long long *ret) +{ + if (value && *value) { + char *end; + long long val = strtoll(value, &end, 0); + unsigned long factor = 1; + + if (!parse_unit_factor(end, &factor)) + return 0; + *ret = val * factor; + return 1; + } + return 0; +} + static int perf_parse_long(const char *value, long *ret) { if (value && *value) { @@ -307,6 +331,15 @@ static void die_bad_config(const char *name) die("bad config value for '%s'", name); } +u64 perf_config_u64(const char *name, const char *value) +{ + long long ret = 0; + + if (!perf_parse_llong(value, &ret)) + die_bad_config(name); + return (u64) ret; +} + int perf_config_int(const char *name, const char *value) { long ret = 0; @@ -350,6 +383,16 @@ 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) { @@ -359,6 +402,12 @@ int perf_default_config(const char *var, const char *value, if (!prefixcmp(var, "hist.")) return perf_hist_config(var, value); + if (!prefixcmp(var, "ui.")) + return perf_ui_config(var, value); + + if (!prefixcmp(var, "call-graph.")) + return perf_callchain_config(var, value); + /* Add other config variables here. */ return 0; } diff --git a/tools/perf/util/data.c b/tools/perf/util/data.c index 55de44ecebef..1921942fc2e0 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) { @@ -49,12 +50,14 @@ static int open_file_read(struct perf_data_file *file) { struct stat st; int fd; + char sbuf[STRERR_BUFSIZE]; fd = open(file->path, O_RDONLY); if (fd < 0) { int err = errno; - pr_err("failed to open %s: %s", file->path, strerror(err)); + pr_err("failed to open %s: %s", file->path, + strerror_r(err, sbuf, sizeof(sbuf))); if (err == ENOENT && !strcmp(file->path, "perf.data")) pr_err(" (try 'perf record' first)"); pr_err("\n"); @@ -65,7 +68,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; } @@ -87,6 +90,7 @@ static int open_file_read(struct perf_data_file *file) static int open_file_write(struct perf_data_file *file) { int fd; + char sbuf[STRERR_BUFSIZE]; if (check_backup(file)) return -1; @@ -94,7 +98,8 @@ static int open_file_write(struct perf_data_file *file) fd = open(file->path, O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR); if (fd < 0) - pr_err("failed to open %s : %s\n", file->path, strerror(errno)); + pr_err("failed to open %s : %s\n", file->path, + strerror_r(errno, sbuf, sizeof(sbuf))); return fd; } diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 299b55586502..ba357f3226c6 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -13,14 +13,18 @@ #include "util.h" #include "target.h" +#define NSECS_PER_SEC 1000000000ULL +#define NSECS_PER_USEC 1000ULL + int verbose; bool dump_trace = false, quiet = false; +int debug_ordered_events; -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,18 +34,47 @@ 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; } +static int __eprintf_time(u64 t, const char *fmt, va_list args) +{ + int ret = 0; + u64 secs, usecs, nsecs = t; + + secs = nsecs / NSECS_PER_SEC; + nsecs -= secs * NSECS_PER_SEC; + usecs = nsecs / NSECS_PER_USEC; + + ret = fprintf(stderr, "[%13" PRIu64 ".%06" PRIu64 "] ", + secs, usecs); + ret += vfprintf(stderr, fmt, args); + return ret; +} + +int eprintf_time(int level, int var, u64 t, const char *fmt, ...) +{ + int ret = 0; + va_list args; + + if (var >= level) { + va_start(args, fmt); + ret = __eprintf_time(t, fmt, args); + va_end(args); + } + + return ret; +} + /* * Overloading libtraceevent standard info print * function, display with -v in perf. @@ -51,9 +84,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 +138,48 @@ 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 = "ordered-events", .ptr = &debug_ordered_events}, + { .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..be264d6f3b30 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -3,6 +3,7 @@ #define __PERF_DEBUG_H #include <stdbool.h> +#include <string.h> #include "event.h" #include "../ui/helpline.h" #include "../ui/progress.h" @@ -10,6 +11,33 @@ extern int verbose; extern bool quiet, dump_trace; +extern int debug_ordered_events; + +#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__) + +#define pr_time_N(n, var, t, fmt, ...) \ + eprintf_time(n, var, t, fmt, ##__VA_ARGS__) + +#define pr_oe_time(t, fmt, ...) pr_time_N(1, debug_ordered_events, t, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_oe_time2(t, fmt, ...) pr_time_N(2, debug_ordered_events, t, pr_fmt(fmt), ##__VA_ARGS__) + +#define STRERR_BUFSIZE 128 /* For the buffer size of strerror_r */ int dump_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void trace_event(union perf_event *event); @@ -19,4 +47,9 @@ 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 eprintf_time(int level, int var, u64 t, const char *fmt, ...) __attribute__((format(printf, 4, 5))); + +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 819f10414f08..0247acfdfaca 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -37,6 +37,7 @@ int dso__read_binary_type_filename(const struct dso *dso, { char build_id_hex[BUILD_ID_SIZE * 2 + 1]; int ret = 0; + size_t len; switch (type) { case DSO_BINARY_TYPE__DEBUGLINK: { @@ -60,26 +61,25 @@ int dso__read_binary_type_filename(const struct dso *dso, break; case DSO_BINARY_TYPE__FEDORA_DEBUGINFO: - snprintf(filename, size, "%s/usr/lib/debug%s.debug", - symbol_conf.symfs, dso->long_name); + len = __symbol__join_symfs(filename, size, "/usr/lib/debug"); + snprintf(filename + len, size - len, "%s.debug", dso->long_name); break; case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO: - snprintf(filename, size, "%s/usr/lib/debug%s", - symbol_conf.symfs, dso->long_name); + len = __symbol__join_symfs(filename, size, "/usr/lib/debug"); + snprintf(filename + len, size - len, "%s", dso->long_name); break; case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO: { const char *last_slash; - size_t len; size_t dir_size; last_slash = dso->long_name + dso->long_name_len; while (last_slash != dso->long_name && *last_slash != '/') last_slash--; - len = scnprintf(filename, size, "%s", symbol_conf.symfs); + len = __symbol__join_symfs(filename, size, ""); dir_size = last_slash - dso->long_name + 2; if (dir_size > (size - len)) { ret = -1; @@ -100,26 +100,24 @@ int dso__read_binary_type_filename(const struct dso *dso, build_id__sprintf(dso->build_id, sizeof(dso->build_id), build_id_hex); - snprintf(filename, size, - "%s/usr/lib/debug/.build-id/%.2s/%s.debug", - symbol_conf.symfs, build_id_hex, build_id_hex + 2); + len = __symbol__join_symfs(filename, size, "/usr/lib/debug/.build-id/"); + snprintf(filename + len, size - len, "%.2s/%s.debug", + build_id_hex, build_id_hex + 2); break; case DSO_BINARY_TYPE__VMLINUX: case DSO_BINARY_TYPE__GUEST_VMLINUX: case DSO_BINARY_TYPE__SYSTEM_PATH_DSO: - snprintf(filename, size, "%s%s", - symbol_conf.symfs, dso->long_name); + __symbol__join_symfs(filename, size, dso->long_name); break; case DSO_BINARY_TYPE__GUEST_KMODULE: - snprintf(filename, size, "%s%s%s", symbol_conf.symfs, - root_dir, dso->long_name); + path__join3(filename, size, symbol_conf.symfs, + root_dir, dso->long_name); break; case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE: - snprintf(filename, size, "%s%s", symbol_conf.symfs, - dso->long_name); + __symbol__join_symfs(filename, size, dso->long_name); break; case DSO_BINARY_TYPE__KCORE: @@ -164,13 +162,15 @@ static void close_first_dso(void); static int do_open(char *name) { int fd; + char sbuf[STRERR_BUFSIZE]; do { fd = open(name, O_RDONLY); if (fd >= 0) return fd; - pr_debug("dso open failed, mmap: %s\n", strerror(errno)); + pr_debug("dso open failed, mmap: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); if (!dso__data_open_cnt || errno != EMFILE) break; @@ -216,7 +216,7 @@ static int open_dso(struct dso *dso, struct machine *machine) { int fd = __open_dso(dso, machine); - if (fd > 0) { + if (fd >= 0) { dso__list_add(dso); /* * Check if we crossed the allowed number @@ -331,26 +331,44 @@ int dso__data_fd(struct dso *dso, struct machine *machine) }; int i = 0; + if (dso->data.status == DSO_DATA_STATUS_ERROR) + return -1; + if (dso->data.fd >= 0) - return dso->data.fd; + goto out; if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) { dso->data.fd = open_dso(dso, machine); - return dso->data.fd; + goto out; } do { - int fd; - dso->binary_type = binary_type_data[i++]; - fd = open_dso(dso, machine); - if (fd >= 0) - return dso->data.fd = 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; +} + +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 -EINVAL; + return false; } static void @@ -514,10 +532,12 @@ static ssize_t cached_read(struct dso *dso, u64 offset, u8 *data, ssize_t size) static int data_file_size(struct dso *dso) { struct stat st; + char sbuf[STRERR_BUFSIZE]; if (!dso->data.file_size) { if (fstat(dso->data.fd, &st)) { - pr_err("dso mmap failed, fstat: %s\n", strerror(errno)); + pr_err("dso mmap failed, fstat: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); return -1; } dso->data.file_size = st.st_size; @@ -526,6 +546,28 @@ static int data_file_size(struct dso *dso) 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) { @@ -611,6 +653,65 @@ struct dso *dso__kernel_findnew(struct machine *machine, const char *name, return dso; } +/* + * Find a matching entry and/or link current entry to RB tree. + * Either one of the dso or name parameter must be non-NULL or the + * function will not work. + */ +static struct dso *dso__findlink_by_longname(struct rb_root *root, + struct dso *dso, const char *name) +{ + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + + if (!name) + name = dso->long_name; + /* + * Find node with the matching name + */ + while (*p) { + struct dso *this = rb_entry(*p, struct dso, rb_node); + int rc = strcmp(name, this->long_name); + + parent = *p; + if (rc == 0) { + /* + * In case the new DSO is a duplicate of an existing + * one, print an one-time warning & put the new entry + * at the end of the list of duplicates. + */ + if (!dso || (dso == this)) + return this; /* Find matching dso */ + /* + * The core kernel DSOs may have duplicated long name. + * In this case, the short name should be different. + * Comparing the short names to differentiate the DSOs. + */ + rc = strcmp(dso->short_name, this->short_name); + if (rc == 0) { + pr_err("Duplicated dso name: %s\n", name); + return NULL; + } + } + if (rc < 0) + p = &parent->rb_left; + else + p = &parent->rb_right; + } + if (dso) { + /* Add new node and rebalance tree */ + rb_link_node(&dso->rb_node, parent, p); + rb_insert_color(&dso->rb_node, root); + } + return NULL; +} + +static inline struct dso * +dso__find_by_longname(const struct rb_root *root, const char *name) +{ + return dso__findlink_by_longname((struct rb_root *)root, NULL, name); +} + void dso__set_long_name(struct dso *dso, const char *name, bool name_allocated) { if (name == NULL) @@ -701,8 +802,10 @@ struct dso *dso__new(const char *name) dso->symbols[i] = dso->symbol_names[i] = 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; @@ -711,6 +814,7 @@ struct dso *dso__new(const char *name) dso->a2l_fails = 1; dso->kernel = DSO_TYPE_USER; dso->needs_swap = DSO_SWAP__UNSET; + RB_CLEAR_NODE(&dso->rb_node); INIT_LIST_HEAD(&dso->node); INIT_LIST_HEAD(&dso->data.open_entry); } @@ -721,6 +825,10 @@ struct dso *dso__new(const char *name) void dso__delete(struct dso *dso) { int i; + + if (!RB_EMPTY_NODE(&dso->rb_node)) + pr_err("DSO %s is still in rbtree when being deleted!\n", + dso->long_name); for (i = 0; i < MAP__NR_TYPES; ++i) symbols__delete(&dso->symbols[i]); @@ -807,35 +915,34 @@ bool __dsos__read_build_ids(struct list_head *head, bool with_hits) return have_build_id; } -void dsos__add(struct list_head *head, struct dso *dso) +void dsos__add(struct dsos *dsos, struct dso *dso) { - list_add_tail(&dso->node, head); + list_add_tail(&dso->node, &dsos->head); + dso__findlink_by_longname(&dsos->root, dso, NULL); } -struct dso *dsos__find(const struct list_head *head, const char *name, bool cmp_short) +struct dso *dsos__find(const struct dsos *dsos, const char *name, + bool cmp_short) { struct dso *pos; if (cmp_short) { - list_for_each_entry(pos, head, node) + list_for_each_entry(pos, &dsos->head, node) if (strcmp(pos->short_name, name) == 0) return pos; return NULL; } - list_for_each_entry(pos, head, node) - if (strcmp(pos->long_name, name) == 0) - return pos; - return NULL; + return dso__find_by_longname(&dsos->root, name); } -struct dso *__dsos__findnew(struct list_head *head, const char *name) +struct dso *__dsos__findnew(struct dsos *dsos, const char *name) { - struct dso *dso = dsos__find(head, name, false); + struct dso *dso = dsos__find(dsos, name, false); if (!dso) { dso = dso__new(name); if (dso != NULL) { - dsos__add(head, dso); + dsos__add(dsos, dso); dso__set_basename(dso); } } @@ -898,3 +1005,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 ad553ba257bf..acb651acc7fd 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -5,6 +5,7 @@ #include <linux/rbtree.h> #include <stdbool.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; \ @@ -72,8 +90,18 @@ struct dso_cache { char data[0]; }; +/* + * DSOs are put into both a list for fast iteration and rbtree for fast + * long name lookup. + */ +struct dsos { + struct list_head head; + struct rb_root root; /* rbtree root sorted by long name */ +}; + struct dso { struct list_head node; + struct rb_node rb_node; /* rbtree node sorted by long name */ struct rb_root symbols[MAP__NR_TYPES]; struct rb_root symbol_names[MAP__NR_TYPES]; void *a2l; @@ -90,6 +118,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; @@ -103,6 +132,8 @@ struct dso { struct { struct rb_root cache; int fd; + int status; + u32 status_seen; size_t file_size; struct list_head open_entry; } data; @@ -153,6 +184,7 @@ int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type t * 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 * @@ -190,20 +222,22 @@ int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type t 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, const char *short_name, int dso_type); -void dsos__add(struct list_head *head, struct dso *dso); -struct dso *dsos__find(const struct list_head *head, const char *name, +void dsos__add(struct dsos *dsos, struct dso *dso); +struct dso *dsos__find(const struct dsos *dsos, const char *name, bool cmp_short); -struct dso *__dsos__findnew(struct list_head *head, const char *name); +struct dso *__dsos__findnew(struct dsos *dsos, const char *name); bool __dsos__read_build_ids(struct list_head *head, bool with_hits); size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp, @@ -229,4 +263,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 d0281bdfa582..4af6b279e34a 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -558,13 +558,17 @@ int perf_event__synthesize_kernel_mmap(struct perf_tool *tool, struct map *map; struct kmap *kmap; int err; + union perf_event *event; + + if (machine->vmlinux_maps[0] == NULL) + return -1; + /* * We should get this from /sys/kernel/sections/.text, but till that is * available use this, and after it is use this as a fallback for older * kernels. */ - union perf_event *event = zalloc((sizeof(event->mmap) + - machine->id_hdr_size)); + event = zalloc((sizeof(event->mmap) + machine->id_hdr_size)); if (event == NULL) { pr_debug("Not enough memory synthesizing mmap event " "for kernel modules\n"); @@ -603,7 +607,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, @@ -777,10 +788,11 @@ try_again: * "[vdso]" dso, but for now lets use the old trick of looking * in the whole kernel symbol list. */ - if ((long long)al->addr < 0 && - cpumode == PERF_RECORD_MISC_USER && - machine && mg != &machine->kmaps) { + if (cpumode == PERF_RECORD_MISC_USER && machine && + mg != &machine->kmaps && + machine__kernel_ip(machine, al->addr)) { mg = &machine->kmaps; + load_map = true; goto try_again; } } else { @@ -866,3 +878,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 e5dd40addb30..7eb7107731ec 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -156,6 +156,8 @@ struct perf_sample { u32 cpu; u32 raw_size; u64 data_src; + u32 flags; + u16 insn_len; void *raw_data; struct ip_callchain *callchain; struct branch_stack *branch_stack; @@ -288,6 +290,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..3cebc9a8d52e 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -25,6 +25,9 @@ #include <linux/bitops.h> #include <linux/hash.h> +static void perf_evlist__mmap_put(struct perf_evlist *evlist, int idx); +static void __perf_evlist__munmap(struct perf_evlist *evlist, int idx); + #define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) #define SID(e, x, y) xyarray__entry(e->sample_id, x, y) @@ -37,6 +40,7 @@ void perf_evlist__init(struct perf_evlist *evlist, struct cpu_map *cpus, INIT_HLIST_HEAD(&evlist->heads[i]); INIT_LIST_HEAD(&evlist->entries); perf_evlist__set_maps(evlist, cpus, threads); + fdarray__init(&evlist->pollfd, 64); evlist->workload.pid = -1; } @@ -102,7 +106,7 @@ static void perf_evlist__purge(struct perf_evlist *evlist) void perf_evlist__exit(struct perf_evlist *evlist) { zfree(&evlist->mmap); - zfree(&evlist->pollfd); + fdarray__exit(&evlist->pollfd); } void perf_evlist__delete(struct perf_evlist *evlist) @@ -122,6 +126,7 @@ void perf_evlist__add(struct perf_evlist *evlist, struct perf_evsel *entry) { list_add_tail(&entry->node, &evlist->entries); entry->idx = evlist->nr_entries; + entry->tracking = !entry->idx; if (!evlist->nr_entries++) perf_evlist__set_id_pos(evlist); @@ -265,17 +270,27 @@ int perf_evlist__add_newtp(struct perf_evlist *evlist, return 0; } +static int perf_evlist__nr_threads(struct perf_evlist *evlist, + struct perf_evsel *evsel) +{ + if (evsel->system_wide) + return 1; + else + return thread_map__nr(evlist->threads); +} + void perf_evlist__disable(struct perf_evlist *evlist) { int cpu, thread; struct perf_evsel *pos; int nr_cpus = cpu_map__nr(evlist->cpus); - int nr_threads = thread_map__nr(evlist->threads); + int nr_threads; for (cpu = 0; cpu < nr_cpus; cpu++) { evlist__for_each(evlist, pos) { if (!perf_evsel__is_group_leader(pos) || !pos->fd) continue; + nr_threads = perf_evlist__nr_threads(evlist, pos); for (thread = 0; thread < nr_threads; thread++) ioctl(FD(pos, cpu, thread), PERF_EVENT_IOC_DISABLE, 0); @@ -288,12 +303,13 @@ void perf_evlist__enable(struct perf_evlist *evlist) int cpu, thread; struct perf_evsel *pos; int nr_cpus = cpu_map__nr(evlist->cpus); - int nr_threads = thread_map__nr(evlist->threads); + int nr_threads; for (cpu = 0; cpu < nr_cpus; cpu++) { evlist__for_each(evlist, pos) { if (!perf_evsel__is_group_leader(pos) || !pos->fd) continue; + nr_threads = perf_evlist__nr_threads(evlist, pos); for (thread = 0; thread < nr_threads; thread++) ioctl(FD(pos, cpu, thread), PERF_EVENT_IOC_ENABLE, 0); @@ -305,12 +321,14 @@ int perf_evlist__disable_event(struct perf_evlist *evlist, struct perf_evsel *evsel) { int cpu, thread, err; + int nr_cpus = cpu_map__nr(evlist->cpus); + int nr_threads = perf_evlist__nr_threads(evlist, evsel); if (!evsel->fd) return 0; - for (cpu = 0; cpu < evlist->cpus->nr; cpu++) { - for (thread = 0; thread < evlist->threads->nr; thread++) { + for (cpu = 0; cpu < nr_cpus; cpu++) { + for (thread = 0; thread < nr_threads; thread++) { err = ioctl(FD(evsel, cpu, thread), PERF_EVENT_IOC_DISABLE, 0); if (err) @@ -324,12 +342,14 @@ int perf_evlist__enable_event(struct perf_evlist *evlist, struct perf_evsel *evsel) { int cpu, thread, err; + int nr_cpus = cpu_map__nr(evlist->cpus); + int nr_threads = perf_evlist__nr_threads(evlist, evsel); if (!evsel->fd) return -EINVAL; - for (cpu = 0; cpu < evlist->cpus->nr; cpu++) { - for (thread = 0; thread < evlist->threads->nr; thread++) { + for (cpu = 0; cpu < nr_cpus; cpu++) { + for (thread = 0; thread < nr_threads; thread++) { err = ioctl(FD(evsel, cpu, thread), PERF_EVENT_IOC_ENABLE, 0); if (err) @@ -339,21 +359,111 @@ int perf_evlist__enable_event(struct perf_evlist *evlist, return 0; } -static int perf_evlist__alloc_pollfd(struct perf_evlist *evlist) +static int perf_evlist__enable_event_cpu(struct perf_evlist *evlist, + struct perf_evsel *evsel, int cpu) +{ + int thread, err; + int nr_threads = perf_evlist__nr_threads(evlist, evsel); + + if (!evsel->fd) + return -EINVAL; + + for (thread = 0; thread < nr_threads; thread++) { + err = ioctl(FD(evsel, cpu, thread), + PERF_EVENT_IOC_ENABLE, 0); + if (err) + return err; + } + return 0; +} + +static int perf_evlist__enable_event_thread(struct perf_evlist *evlist, + struct perf_evsel *evsel, + int thread) +{ + int cpu, err; + int nr_cpus = cpu_map__nr(evlist->cpus); + + if (!evsel->fd) + return -EINVAL; + + for (cpu = 0; cpu < nr_cpus; cpu++) { + err = ioctl(FD(evsel, cpu, thread), PERF_EVENT_IOC_ENABLE, 0); + if (err) + return err; + } + return 0; +} + +int perf_evlist__enable_event_idx(struct perf_evlist *evlist, + struct perf_evsel *evsel, int idx) +{ + bool per_cpu_mmaps = !cpu_map__empty(evlist->cpus); + + if (per_cpu_mmaps) + return perf_evlist__enable_event_cpu(evlist, evsel, idx); + else + return perf_evlist__enable_event_thread(evlist, evsel, idx); +} + +int perf_evlist__alloc_pollfd(struct perf_evlist *evlist) { int nr_cpus = cpu_map__nr(evlist->cpus); int nr_threads = thread_map__nr(evlist->threads); - int nfds = nr_cpus * nr_threads * evlist->nr_entries; - evlist->pollfd = malloc(sizeof(struct pollfd) * nfds); - return evlist->pollfd != NULL ? 0 : -ENOMEM; + int nfds = 0; + struct perf_evsel *evsel; + + list_for_each_entry(evsel, &evlist->entries, node) { + if (evsel->system_wide) + nfds += nr_cpus; + else + nfds += nr_cpus * nr_threads; + } + + if (fdarray__available_entries(&evlist->pollfd) < nfds && + fdarray__grow(&evlist->pollfd, nfds) < 0) + return -ENOMEM; + + return 0; +} + +static int __perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd, int idx) +{ + int pos = fdarray__add(&evlist->pollfd, fd, POLLIN | POLLERR | POLLHUP); + /* + * Save the idx so that when we filter out fds POLLHUP'ed we can + * close the associated evlist->mmap[] entry. + */ + if (pos >= 0) { + evlist->pollfd.priv[pos].idx = idx; + + fcntl(fd, F_SETFL, O_NONBLOCK); + } + + return pos; +} + +int perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd) +{ + return __perf_evlist__add_pollfd(evlist, fd, -1); +} + +static void perf_evlist__munmap_filtered(struct fdarray *fda, int fd) +{ + struct perf_evlist *evlist = container_of(fda, struct perf_evlist, pollfd); + + perf_evlist__mmap_put(evlist, fda->priv[fd].idx); } -void perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd) +int perf_evlist__filter_pollfd(struct perf_evlist *evlist, short revents_and_mask) { - fcntl(fd, F_SETFL, O_NONBLOCK); - evlist->pollfd[evlist->nr_fds].fd = fd; - evlist->pollfd[evlist->nr_fds].events = POLLIN; - evlist->nr_fds++; + return fdarray__filter(&evlist->pollfd, revents_and_mask, + perf_evlist__munmap_filtered); +} + +int perf_evlist__poll(struct perf_evlist *evlist, int timeout) +{ + return fdarray__poll(&evlist->pollfd, timeout); } static void perf_evlist__id_hash(struct perf_evlist *evlist, @@ -566,14 +676,36 @@ union perf_event *perf_evlist__mmap_read(struct perf_evlist *evlist, int idx) return event; } +static bool perf_mmap__empty(struct perf_mmap *md) +{ + return perf_mmap__read_head(md) != md->prev; +} + +static void perf_evlist__mmap_get(struct perf_evlist *evlist, int idx) +{ + ++evlist->mmap[idx].refcnt; +} + +static void perf_evlist__mmap_put(struct perf_evlist *evlist, int idx) +{ + BUG_ON(evlist->mmap[idx].refcnt == 0); + + if (--evlist->mmap[idx].refcnt == 0) + __perf_evlist__munmap(evlist, idx); +} + void perf_evlist__mmap_consume(struct perf_evlist *evlist, int idx) { + struct perf_mmap *md = &evlist->mmap[idx]; + if (!evlist->overwrite) { - struct perf_mmap *md = &evlist->mmap[idx]; unsigned int old = md->prev; perf_mmap__write_tail(md, old); } + + if (md->refcnt == 1 && perf_mmap__empty(md)) + perf_evlist__mmap_put(evlist, idx); } static void __perf_evlist__munmap(struct perf_evlist *evlist, int idx) @@ -581,6 +713,7 @@ static void __perf_evlist__munmap(struct perf_evlist *evlist, int idx) if (evlist->mmap[idx].base != NULL) { munmap(evlist->mmap[idx].base, evlist->mmap_len); evlist->mmap[idx].base = NULL; + evlist->mmap[idx].refcnt = 0; } } @@ -606,12 +739,31 @@ 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) { + /* + * The last one will be done at perf_evlist__mmap_consume(), so that we + * make sure we don't prevent tools from consuming every last event in + * the ring buffer. + * + * I.e. we can get the POLLHUP meaning that the fd doesn't exist + * anymore, but the last events for it are still in the ring buffer, + * waiting to be consumed. + * + * Tools can chose to ignore this at their own discretion, but the + * evlist layer can't just drop it when filtering events in + * perf_evlist__filter_pollfd(). + */ + evlist->mmap[idx].refcnt = 2; 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", @@ -620,27 +772,37 @@ static int __perf_evlist__mmap(struct perf_evlist *evlist, return -1; } - perf_evlist__add_pollfd(evlist, fd); return 0; } 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; evlist__for_each(evlist, evsel) { - int fd = FD(evsel, cpu, thread); + int fd; + + if (evsel->system_wide && thread) + continue; + + fd = FD(evsel, cpu, thread); 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) return -1; + + perf_evlist__mmap_get(evlist, idx); + } + + if (__perf_evlist__add_pollfd(evlist, fd, idx) < 0) { + perf_evlist__mmap_put(evlist, idx); + return -1; } if ((evsel->attr.read_format & PERF_FORMAT_ID) && @@ -651,8 +813,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 +825,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 +839,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 +849,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,18 +955,20 @@ 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; - if (evlist->pollfd == NULL && perf_evlist__alloc_pollfd(evlist) < 0) + if (evlist->pollfd.entries == NULL && perf_evlist__alloc_pollfd(evlist) < 0) return -ENOMEM; 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 +978,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) @@ -1055,6 +1219,8 @@ int perf_evlist__prepare_workload(struct perf_evlist *evlist, struct target *tar } if (!evlist->workload.pid) { + int ret; + if (pipe_output) dup2(2, 1); @@ -1072,8 +1238,22 @@ int perf_evlist__prepare_workload(struct perf_evlist *evlist, struct target *tar /* * Wait until the parent tells us to go. */ - if (read(go_pipe[0], &bf, 1) == -1) - perror("unable to read pipe"); + ret = read(go_pipe[0], &bf, 1); + /* + * The parent will ask for the execvp() to be performed by + * writing exactly one byte, in workload.cork_fd, usually via + * perf_evlist__start_workload(). + * + * For cancelling the workload without actuallin running it, + * the parent will just close workload.cork_fd, without writing + * anything, i.e. read will return zero and we just exit() + * here. + */ + if (ret != 1) { + if (ret == -1) + perror("unable to read pipe"); + exit(ret); + } execvp(argv[0], (char **)argv); @@ -1196,7 +1376,7 @@ int perf_evlist__strerror_open(struct perf_evlist *evlist __maybe_unused, int err, char *buf, size_t size) { int printed, value; - char sbuf[128], *emsg = strerror_r(err, sbuf, sizeof(sbuf)); + char sbuf[STRERR_BUFSIZE], *emsg = strerror_r(err, sbuf, sizeof(sbuf)); switch (err) { case EACCES: @@ -1214,10 +1394,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); @@ -1243,3 +1424,19 @@ void perf_evlist__to_front(struct perf_evlist *evlist, list_splice(&move, &evlist->entries); } + +void perf_evlist__set_tracking_event(struct perf_evlist *evlist, + struct perf_evsel *tracking_evsel) +{ + struct perf_evsel *evsel; + + if (tracking_evsel->tracking) + return; + + evlist__for_each(evlist, evsel) { + if (evsel != tracking_evsel) + evsel->tracking = false; + } + + tracking_evsel->tracking = true; +} diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h index f5173cd63693..bd312b01e876 100644 --- a/tools/perf/util/evlist.h +++ b/tools/perf/util/evlist.h @@ -2,6 +2,7 @@ #define __PERF_EVLIST_H 1 #include <linux/list.h> +#include <api/fd/array.h> #include <stdio.h> #include "../perf.h" #include "event.h" @@ -17,9 +18,15 @@ struct record_opts; #define PERF_EVLIST__HLIST_BITS 8 #define PERF_EVLIST__HLIST_SIZE (1 << PERF_EVLIST__HLIST_BITS) +/** + * struct perf_mmap - perf's ring buffer mmap details + * + * @refcnt - e.g. code using PERF_EVENT_IOC_SET_OUTPUT to share this + */ struct perf_mmap { void *base; int mask; + int refcnt; unsigned int prev; char event_copy[PERF_SAMPLE_MAX_SIZE]; }; @@ -29,7 +36,6 @@ struct perf_evlist { struct hlist_head heads[PERF_EVLIST__HLIST_SIZE]; int nr_entries; int nr_groups; - int nr_fds; int nr_mmaps; size_t mmap_len; int id_pos; @@ -40,8 +46,8 @@ struct perf_evlist { pid_t pid; } workload; bool overwrite; + struct fdarray pollfd; struct perf_mmap *mmap; - struct pollfd *pollfd; struct thread_map *threads; struct cpu_map *cpus; struct perf_evsel *selected; @@ -82,7 +88,11 @@ perf_evlist__find_tracepoint_by_name(struct perf_evlist *evlist, void perf_evlist__id_add(struct perf_evlist *evlist, struct perf_evsel *evsel, int cpu, int thread, u64 id); -void perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd); +int perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd); +int perf_evlist__alloc_pollfd(struct perf_evlist *evlist); +int perf_evlist__filter_pollfd(struct perf_evlist *evlist, short revents_and_mask); + +int perf_evlist__poll(struct perf_evlist *evlist, int timeout); struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id); @@ -122,6 +132,8 @@ int perf_evlist__disable_event(struct perf_evlist *evlist, struct perf_evsel *evsel); int perf_evlist__enable_event(struct perf_evlist *evlist, struct perf_evsel *evsel); +int perf_evlist__enable_event_idx(struct perf_evlist *evlist, + struct perf_evsel *evsel, int idx); void perf_evlist__set_selected(struct perf_evlist *evlist, struct perf_evsel *evsel); @@ -262,4 +274,7 @@ void perf_evlist__to_front(struct perf_evlist *evlist, #define evlist__for_each_safe(evlist, tmp, evsel) \ __evlist__for_each_safe(&(evlist)->entries, tmp, evsel) +void perf_evlist__set_tracking_event(struct perf_evlist *evlist, + struct perf_evsel *tracking_evsel); + #endif /* __PERF_EVLIST_H */ diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 8606175fe1e8..e0868a901c4a 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)) @@ -161,6 +162,7 @@ void perf_evsel__init(struct perf_evsel *evsel, struct perf_event_attr *attr, int idx) { evsel->idx = idx; + evsel->tracking = !idx; evsel->attr = *attr; evsel->leader = evsel; evsel->unit = ""; @@ -501,20 +503,19 @@ int perf_evsel__group_desc(struct perf_evsel *evsel, char *buf, size_t size) } static void -perf_evsel__config_callgraph(struct perf_evsel *evsel, - struct record_opts *opts) +perf_evsel__config_callgraph(struct perf_evsel *evsel) { bool function = perf_evsel__is_function_event(evsel); struct perf_event_attr *attr = &evsel->attr; perf_evsel__set_sample_bit(evsel, CALLCHAIN); - if (opts->call_graph == CALLCHAIN_DWARF) { + if (callchain_param.record_mode == CALLCHAIN_DWARF) { if (!function) { perf_evsel__set_sample_bit(evsel, REGS_USER); perf_evsel__set_sample_bit(evsel, STACK_USER); attr->sample_regs_user = PERF_REGS_MASK; - attr->sample_stack_user = opts->stack_dump_size; + attr->sample_stack_user = callchain_param.dump_size; attr->exclude_callchain_user = 1; } else { pr_info("Cannot use DWARF unwind for function trace event," @@ -560,7 +561,7 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) { struct perf_evsel *leader = evsel->leader; struct perf_event_attr *attr = &evsel->attr; - int track = !evsel->idx; /* only the first counter needs these */ + int track = evsel->tracking; bool per_cpu = opts->target.default_per_cpu && !opts->target.per_thread; attr->sample_id_all = perf_missing_features.sample_id_all ? 0 : 1; @@ -623,8 +624,8 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) attr->mmap_data = track; } - if (opts->call_graph_enabled) - perf_evsel__config_callgraph(evsel, opts); + if (callchain_param.enabled && !evsel->no_aux_samples) + perf_evsel__config_callgraph(evsel); if (target__has_cpu(&opts->target)) perf_evsel__set_sample_bit(evsel, CPU); @@ -632,12 +633,15 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) if (opts->period) perf_evsel__set_sample_bit(evsel, PERIOD); - if (!perf_missing_features.sample_id_all && - (opts->sample_time || !opts->no_inherit || - target__has_cpu(&opts->target) || per_cpu)) + /* + * When the user explicitely disabled time don't force it here. + */ + if (opts->sample_time && + (!perf_missing_features.sample_id_all && + (!opts->no_inherit || 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 +654,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; } @@ -681,11 +685,20 @@ 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) { int cpu, thread; + + if (evsel->system_wide) + nthreads = 1; + evsel->fd = xyarray__new(ncpus, nthreads, sizeof(int)); if (evsel->fd) { @@ -704,6 +717,9 @@ static int perf_evsel__run_ioctl(struct perf_evsel *evsel, int ncpus, int nthrea { int cpu, thread; + if (evsel->system_wide) + nthreads = 1; + for (cpu = 0; cpu < ncpus; cpu++) { for (thread = 0; thread < nthreads; thread++) { int fd = FD(evsel, cpu, thread), @@ -734,6 +750,9 @@ int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads) int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads) { + if (evsel->system_wide) + nthreads = 1; + evsel->sample_id = xyarray__new(ncpus, nthreads, sizeof(struct perf_sample_id)); if (evsel->sample_id == NULL) return -ENOMEM; @@ -778,6 +797,9 @@ void perf_evsel__close_fd(struct perf_evsel *evsel, int ncpus, int nthreads) { int cpu, thread; + if (evsel->system_wide) + nthreads = 1; + for (cpu = 0; cpu < ncpus; cpu++) for (thread = 0; thread < nthreads; ++thread) { close(FD(evsel, cpu, thread)); @@ -866,6 +888,9 @@ int __perf_evsel__read(struct perf_evsel *evsel, int cpu, thread; struct perf_counts_values *aggr = &evsel->counts->aggr, count; + if (evsel->system_wide) + nthreads = 1; + aggr->val = aggr->ena = aggr->run = 0; for (cpu = 0; cpu < ncpus; cpu++) { @@ -960,6 +985,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); @@ -967,7 +993,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,21 +1013,28 @@ static size_t perf_event_attr__fprintf(struct perf_event_attr *attr, FILE *fp) static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, struct thread_map *threads) { - int cpu, thread; - unsigned long flags = 0; + int cpu, thread, nthreads; + unsigned long flags = PERF_FLAG_FD_CLOEXEC; int pid = -1, err; enum { NO_CHANGE, SET_TO_MAX, INCREASED_MAX } set_rlimit = NO_CHANGE; + if (evsel->system_wide) + nthreads = 1; + else + nthreads = threads->nr; + if (evsel->fd == NULL && - perf_evsel__alloc_fd(evsel, cpus->nr, threads->nr) < 0) + perf_evsel__alloc_fd(evsel, cpus->nr, nthreads) < 0) 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) @@ -1016,10 +1048,10 @@ retry_sample_id: for (cpu = 0; cpu < cpus->nr; cpu++) { - for (thread = 0; thread < threads->nr; thread++) { + for (thread = 0; thread < nthreads; thread++) { int group_fd; - if (!evsel->cgrp) + if (!evsel->cgrp && !evsel->system_wide) pid = threads->map[thread]; group_fd = get_group_fd(evsel, cpu, thread); @@ -1071,7 +1103,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 && @@ -1089,7 +1124,7 @@ out_close: close(FD(evsel, cpu, thread)); FD(evsel, cpu, thread) = -1; } - thread = threads->nr; + thread = nthreads; } while (--cpu >= 0); return err; } @@ -1940,6 +1975,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); @@ -1990,6 +2026,8 @@ bool perf_evsel__fallback(struct perf_evsel *evsel, int err, int perf_evsel__open_strerror(struct perf_evsel *evsel, struct target *target, int err, char *msg, size_t size) { + char sbuf[STRERR_BUFSIZE]; + switch (err) { case EPERM: case EACCES: @@ -2024,13 +2062,20 @@ int perf_evsel__open_strerror(struct perf_evsel *evsel, struct target *target, "No APIC? If so then you can boot the kernel with the \"lapic\" boot parameter to force-enable it."); #endif break; + case EBUSY: + if (find_process("oprofiled")) + return scnprintf(msg, size, + "The PMU counters are busy/taken by another profiler.\n" + "We found oprofile daemon running, please stop it and try again."); + break; default: break; } return scnprintf(msg, size, - "The sys_perf_event_open() syscall returned with %d (%s) for event (%s). \n" + "The sys_perf_event_open() syscall returned with %d (%s) for event (%s).\n" "/bin/dmesg may provide additional information.\n" "No CONFIG_PERF_EVENTS=y kernel support configured?\n", - err, strerror(err), perf_evsel__name(evsel)); + err, strerror_r(err, sbuf, sizeof(sbuf)), + perf_evsel__name(evsel)); } diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index a52e9a5bb2d0..7bc314be6a7b 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -83,6 +83,10 @@ struct perf_evsel { int is_pos; bool supported; bool needs_swap; + bool no_aux_samples; + bool immediate; + bool system_wide; + bool tracking; /* parse modifier helper */ int exclude_GH; int nr_members; diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 893f8e2df928..ce0de00399da 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.head); + if (err) + return err; + + return __dsos__hit_all(&machine->user_dsos.head); +} + +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; @@ -247,11 +288,12 @@ static int machine__write_buildid_table(struct machine *machine, int fd) umisc = PERF_RECORD_MISC_GUEST_USER; } - err = __dsos__write_buildid_table(&machine->kernel_dsos, machine, + err = __dsos__write_buildid_table(&machine->kernel_dsos.head, machine, machine->pid, kmisc, fd); if (err == 0) - err = __dsos__write_buildid_table(&machine->user_dsos, machine, - machine->pid, umisc, fd); + err = __dsos__write_buildid_table(&machine->user_dsos.head, + machine, machine->pid, umisc, + fd); return err; } @@ -298,7 +340,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 +428,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]; @@ -414,9 +456,10 @@ static int __dsos__cache_build_ids(struct list_head *head, static int machine__cache_build_ids(struct machine *machine, const char *debugdir) { - int ret = __dsos__cache_build_ids(&machine->kernel_dsos, machine, + int ret = __dsos__cache_build_ids(&machine->kernel_dsos.head, machine, debugdir); - ret |= __dsos__cache_build_ids(&machine->user_dsos, machine, debugdir); + ret |= __dsos__cache_build_ids(&machine->user_dsos.head, machine, + debugdir); return ret; } @@ -442,8 +485,10 @@ static int perf_session__cache_build_ids(struct perf_session *session) static bool machine__read_build_ids(struct machine *machine, bool with_hits) { - bool ret = __dsos__read_build_ids(&machine->kernel_dsos, with_hits); - ret |= __dsos__read_build_ids(&machine->user_dsos, with_hits); + bool ret; + + ret = __dsos__read_build_ids(&machine->kernel_dsos.head, with_hits); + ret |= __dsos__read_build_ids(&machine->user_dsos.head, with_hits); return ret; } @@ -1507,7 +1552,7 @@ static int __event_process_build_id(struct build_id_event *bev, struct perf_session *session) { int err = -1; - struct list_head *head; + struct dsos *dsos; struct machine *machine; u16 misc; struct dso *dso; @@ -1522,22 +1567,22 @@ static int __event_process_build_id(struct build_id_event *bev, switch (misc) { case PERF_RECORD_MISC_KERNEL: dso_type = DSO_TYPE_KERNEL; - head = &machine->kernel_dsos; + dsos = &machine->kernel_dsos; break; case PERF_RECORD_MISC_GUEST_KERNEL: dso_type = DSO_TYPE_GUEST_KERNEL; - head = &machine->kernel_dsos; + dsos = &machine->kernel_dsos; break; case PERF_RECORD_MISC_USER: case PERF_RECORD_MISC_GUEST_USER: dso_type = DSO_TYPE_USER; - head = &machine->user_dsos; + dsos = &machine->user_dsos; break; default: goto out; } - dso = __dsos__findnew(head, filename); + dso = __dsos__findnew(dsos, filename); if (dso != NULL) { char sbuild_id[BUILD_ID_SIZE * 2 + 1]; diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index d08cfe499404..8f5cbaea64a5 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -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 30df6187ee02..86569fa3651d 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -277,6 +277,28 @@ void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel) } } +void hists__delete_entries(struct hists *hists) +{ + struct rb_node *next = rb_first(&hists->entries); + struct hist_entry *n; + + while (next) { + n = rb_entry(next, struct hist_entry, rb_node); + next = rb_next(&n->rb_node); + + rb_erase(&n->rb_node, &hists->entries); + + if (sort__need_collapse) + rb_erase(&n->rb_node_in, &hists->entries_collapsed); + + --hists->nr_entries; + if (!n->filtered) + --hists->nr_non_filtered_entries; + + hist_entry__free(n); + } +} + /* * histogram, sorted on item, collects periods */ diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 742f49a85725..8c9c70e18cbb 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -152,6 +152,7 @@ void hists__output_resort(struct hists *hists); 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__delete_entries(struct hists *hists); void hists__output_recalc_col_len(struct hists *hists, int max_rows); u64 hists__total_period(struct hists *hists); @@ -192,6 +193,7 @@ struct perf_hpp { }; struct perf_hpp_fmt { + const char *name; int (*header)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct perf_evsel *evsel); int (*width)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, @@ -207,6 +209,8 @@ struct perf_hpp_fmt { struct list_head list; struct list_head sort_list; bool elide; + int len; + int user_len; }; extern struct list_head perf_hpp__list; @@ -261,17 +265,19 @@ static inline bool perf_hpp__should_skip(struct perf_hpp_fmt *format) } void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists); +void perf_hpp__reset_sort_width(struct perf_hpp_fmt *fmt, struct hists *hists); +void perf_hpp__set_user_width(const char *width_list_str); 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, 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); +int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he, hpp_field_fn get_field, + const char *fmtstr, hpp_snprint_fn print_fn, bool fmt_percent); +int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he, hpp_field_fn get_field, + const char *fmtstr, hpp_snprint_fn print_fn, bool fmt_percent); static inline void advance_hpp(struct perf_hpp *hpp, int inc) { 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/kvm-stat.h b/tools/perf/util/kvm-stat.h new file mode 100644 index 000000000000..cf1d7913783b --- /dev/null +++ b/tools/perf/util/kvm-stat.h @@ -0,0 +1,139 @@ +#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; + + 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 c73e1fc12e53..b7d477fbda02 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" @@ -16,25 +17,29 @@ int machine__init(struct machine *machine, const char *root_dir, pid_t pid) { map_groups__init(&machine->kmaps); RB_CLEAR_NODE(&machine->rb_node); - INIT_LIST_HEAD(&machine->user_dsos); - INIT_LIST_HEAD(&machine->kernel_dsos); + INIT_LIST_HEAD(&machine->user_dsos.head); + INIT_LIST_HEAD(&machine->kernel_dsos.head); machine->threads = RB_ROOT; INIT_LIST_HEAD(&machine->dead_threads); machine->last_match = NULL; + machine->vdso_info = NULL; + machine->kmaps.machine = machine; machine->pid = pid; machine->symbol_filter = NULL; machine->id_hdr_size = 0; + machine->comm_exec = false; + machine->kernel_start = 0; machine->root_dir = strdup(root_dir); if (machine->root_dir == NULL) 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 +50,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; } @@ -65,11 +72,12 @@ out_delete: return NULL; } -static void dsos__delete(struct list_head *dsos) +static void dsos__delete(struct dsos *dsos) { struct dso *pos, *n; - list_for_each_entry_safe(pos, n, dsos, node) { + list_for_each_entry_safe(pos, n, &dsos->head, node) { + RB_CLEAR_NODE(&pos->rb_node); list_del(&pos->node); dso__delete(pos); } @@ -103,7 +111,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) @@ -172,6 +182,19 @@ void machines__set_symbol_filter(struct machines *machines, } } +void machines__set_comm_exec(struct machines *machines, bool comm_exec) +{ + struct rb_node *nd; + + machines->host.comm_exec = comm_exec; + + for (nd = rb_first(&machines->guests); nd; nd = rb_next(nd)) { + struct machine *machine = rb_entry(nd, struct machine, rb_node); + + machine->comm_exec = comm_exec; + } +} + struct machine *machines__find(struct machines *machines, pid_t pid) { struct rb_node **p = &machines->guests.rb_node; @@ -272,6 +295,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 +354,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 +366,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; } @@ -325,8 +393,10 @@ static struct thread *__machine__findnew_thread(struct machine *machine, * within thread__init_map_groups to find the thread * leader and that would screwed the rb tree. */ - if (thread__init_map_groups(th, machine)) + if (thread__init_map_groups(th, machine)) { + thread__delete(th); return NULL; + } } return th; @@ -344,17 +414,31 @@ struct thread *machine__find_thread(struct machine *machine, pid_t pid, return __machine__findnew_thread(machine, pid, tid, false); } +struct comm *machine__thread_exec_comm(struct machine *machine, + struct thread *thread) +{ + if (machine->comm_exec) + return thread__exec_comm(thread); + else + return thread__comm(thread); +} + int machine__process_comm_event(struct machine *machine, union perf_event *event, struct perf_sample *sample) { struct thread *thread = machine__findnew_thread(machine, event->comm.pid, event->comm.tid); + bool exec = event->header.misc & PERF_RECORD_MISC_COMM_EXEC; + + if (exec) + machine->comm_exec = true; if (dump_trace) perf_event__fprintf_comm(event, stdout); - if (thread == NULL || thread__set_comm(thread, event->comm.comm, sample->time)) { + if (thread == NULL || + __thread__set_comm(thread, event->comm.comm, sample->time, exec)) { dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n"); return -1; } @@ -394,23 +478,23 @@ struct map *machine__new_module(struct machine *machine, u64 start, size_t machines__fprintf_dsos(struct machines *machines, FILE *fp) { struct rb_node *nd; - size_t ret = __dsos__fprintf(&machines->host.kernel_dsos, fp) + - __dsos__fprintf(&machines->host.user_dsos, fp); + size_t ret = __dsos__fprintf(&machines->host.kernel_dsos.head, fp) + + __dsos__fprintf(&machines->host.user_dsos.head, fp); for (nd = rb_first(&machines->guests); nd; nd = rb_next(nd)) { struct machine *pos = rb_entry(nd, struct machine, rb_node); - ret += __dsos__fprintf(&pos->kernel_dsos, fp); - ret += __dsos__fprintf(&pos->user_dsos, fp); + ret += __dsos__fprintf(&pos->kernel_dsos.head, fp); + ret += __dsos__fprintf(&pos->user_dsos.head, fp); } return ret; } -size_t machine__fprintf_dsos_buildid(struct machine *machine, FILE *fp, +size_t machine__fprintf_dsos_buildid(struct machine *m, FILE *fp, bool (skip)(struct dso *dso, int parm), int parm) { - return __dsos__fprintf_buildid(&machine->kernel_dsos, fp, skip, parm) + - __dsos__fprintf_buildid(&machine->user_dsos, fp, skip, parm); + return __dsos__fprintf_buildid(&m->kernel_dsos.head, fp, skip, parm) + + __dsos__fprintf_buildid(&m->user_dsos.head, fp, skip, parm); } size_t machines__fprintf_dsos_buildid(struct machines *machines, FILE *fp, @@ -511,8 +595,8 @@ const char *ref_reloc_sym_names[] = {"_text", "_stext", NULL}; * 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) +static u64 machine__get_running_kernel_start(struct machine *machine, + const char **symbol_name) { char filename[PATH_MAX]; int i; @@ -539,7 +623,7 @@ static u64 machine__get_kernel_start_addr(struct machine *machine, int __machine__create_kernel_maps(struct machine *machine, struct dso *kernel) { enum map_type type; - u64 start = machine__get_kernel_start_addr(machine, NULL); + u64 start = machine__get_running_kernel_start(machine, NULL); for (type = 0; type < MAP__NR_TYPES; ++type) { struct kmap *kmap; @@ -858,7 +942,7 @@ int machine__create_kernel_maps(struct machine *machine) { struct dso *kernel = machine__get_kernel(machine); const char *name; - u64 addr = machine__get_kernel_start_addr(machine, &name); + u64 addr = machine__get_running_kernel_start(machine, &name); if (!addr) return -1; @@ -911,7 +995,7 @@ static bool machine__uses_kcore(struct machine *machine) { struct dso *dso; - list_for_each_entry(dso, &machine->kernel_dsos, node) { + list_for_each_entry(dso, &machine->kernel_dsos.head, node) { if (dso__is_kcore(dso)) return true; } @@ -1045,14 +1129,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.prot, event->mmap2.flags, - event->mmap2.filename, type); + event->mmap2.filename, type, thread); if (map == NULL) goto out_problem; @@ -1095,11 +1179,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, 0, 0, event->mmap.filename, - type); + type, thread); if (map == NULL) goto out_problem; @@ -1231,6 +1315,16 @@ static void ip__resolve_data(struct machine *machine, struct thread *thread, thread__find_addr_location(thread, machine, m, MAP__VARIABLE, addr, &al); + if (al.map == NULL) { + /* + * some shared data regions have execute bit set which puts + * their mapping in the MAP__FUNCTION type array. + * Check there as a fallback option before dropping the sample. + */ + thread__find_addr_location(thread, machine, m, MAP__FUNCTION, addr, + &al); + } + ams->addr = addr; ams->al_addr = al.addr; ams->sym = al.sym; @@ -1281,7 +1375,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); @@ -1290,14 +1386,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) { @@ -1420,3 +1528,68 @@ 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; +} + +int machine__get_kernel_start(struct machine *machine) +{ + struct map *map = machine__kernel_map(machine, MAP__FUNCTION); + int err = 0; + + /* + * The only addresses above 2^63 are kernel addresses of a 64-bit + * kernel. Note that addresses are unsigned so that on a 32-bit system + * all addresses including kernel addresses are less than 2^32. In + * that case (32-bit system), if the kernel mapping is unknown, all + * addresses will be assumed to be in user space - see + * machine__kernel_ip(). + */ + machine->kernel_start = 1ULL << 63; + if (map) { + err = map__load(map, machine->symbol_filter); + if (map->start) + machine->kernel_start = map->start; + } + return err; +} diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h index c8c74a119398..2b651a7f5d0d 100644 --- a/tools/perf/util/machine.h +++ b/tools/perf/util/machine.h @@ -4,6 +4,7 @@ #include <sys/types.h> #include <linux/rbtree.h> #include "map.h" +#include "dso.h" #include "event.h" struct addr_location; @@ -20,19 +21,25 @@ union perf_event; extern const char *ref_reloc_sym_names[]; +struct vdso_info; + struct machine { struct rb_node rb_node; pid_t pid; u16 id_hdr_size; + bool comm_exec; char *root_dir; struct rb_root threads; struct list_head dead_threads; struct thread *last_match; - struct list_head user_dsos; - struct list_head kernel_dsos; + struct vdso_info *vdso_info; + struct dsos user_dsos; + struct dsos kernel_dsos; struct map_groups kmaps; struct map *vmlinux_maps[MAP__NR_TYPES]; + u64 kernel_start; symbol_filter_t symbol_filter; + pid_t *current_tid; }; static inline @@ -41,8 +48,26 @@ struct map *machine__kernel_map(struct machine *machine, enum map_type type) return machine->vmlinux_maps[type]; } +int machine__get_kernel_start(struct machine *machine); + +static inline u64 machine__kernel_start(struct machine *machine) +{ + if (!machine->kernel_start) + machine__get_kernel_start(machine); + return machine->kernel_start; +} + +static inline bool machine__kernel_ip(struct machine *machine, u64 ip) +{ + u64 kernel_start = machine__kernel_start(machine); + + return ip >= kernel_start; +} + struct thread *machine__find_thread(struct machine *machine, pid_t pid, pid_t tid); +struct comm *machine__thread_exec_comm(struct machine *machine, + struct thread *thread); int machine__process_comm_event(struct machine *machine, union perf_event *event, struct perf_sample *sample); @@ -84,6 +109,7 @@ char *machine__mmap_name(struct machine *machine, char *bf, size_t size); void machines__set_symbol_filter(struct machines *machines, symbol_filter_t symbol_filter); +void machines__set_comm_exec(struct machines *machines, bool comm_exec); struct machine *machine__new_host(void); int machine__init(struct machine *machine, const char *root_dir, pid_t pid); @@ -191,4 +217,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 25c571f4cba6..b7090596ac50 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] = { @@ -29,6 +31,7 @@ static inline int is_anon_memory(const char *filename) static inline int is_no_dso_memory(const char *filename) { return !strncmp(filename, "[stack", 6) || + !strncmp(filename, "/SYSV",5) || !strcmp(filename, "[heap]"); } @@ -136,10 +139,10 @@ 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, u32 prot, u32 flags, char *filename, - enum map_type type) + enum map_type type, struct thread *thread) { struct map *map = malloc(sizeof(*map)); @@ -172,9 +175,9 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, 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; @@ -454,6 +457,20 @@ 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)); @@ -554,8 +571,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; @@ -573,17 +590,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; @@ -600,23 +616,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 7758c72522ef..2f83954af050 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -66,6 +66,7 @@ struct map_groups { 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) { @@ -103,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 * @@ -118,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, u32 prot, u32 flags, - char *filename, enum map_type type); + 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); @@ -141,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); @@ -152,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); @@ -210,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/ordered-events.c b/tools/perf/util/ordered-events.c new file mode 100644 index 000000000000..706ce1a66169 --- /dev/null +++ b/tools/perf/util/ordered-events.c @@ -0,0 +1,245 @@ +#include <linux/list.h> +#include <linux/compiler.h> +#include "ordered-events.h" +#include "evlist.h" +#include "session.h" +#include "asm/bug.h" +#include "debug.h" + +#define pr_N(n, fmt, ...) \ + eprintf(n, debug_ordered_events, fmt, ##__VA_ARGS__) + +#define pr(fmt, ...) pr_N(1, pr_fmt(fmt), ##__VA_ARGS__) + +static void queue_event(struct ordered_events *oe, struct ordered_event *new) +{ + struct ordered_event *last = oe->last; + u64 timestamp = new->timestamp; + struct list_head *p; + + ++oe->nr_events; + oe->last = new; + + pr_oe_time2(timestamp, "queue_event nr_events %u\n", oe->nr_events); + + if (!last) { + list_add(&new->list, &oe->events); + oe->max_timestamp = timestamp; + return; + } + + /* + * last event might point to some random place in the list as it's + * the last queued event. We expect that the new event is close to + * this. + */ + if (last->timestamp <= timestamp) { + while (last->timestamp <= timestamp) { + p = last->list.next; + if (p == &oe->events) { + list_add_tail(&new->list, &oe->events); + oe->max_timestamp = timestamp; + return; + } + last = list_entry(p, struct ordered_event, list); + } + list_add_tail(&new->list, &last->list); + } else { + while (last->timestamp > timestamp) { + p = last->list.prev; + if (p == &oe->events) { + list_add(&new->list, &oe->events); + return; + } + last = list_entry(p, struct ordered_event, list); + } + list_add(&new->list, &last->list); + } +} + +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event)) +static struct ordered_event *alloc_event(struct ordered_events *oe) +{ + struct list_head *cache = &oe->cache; + struct ordered_event *new = NULL; + + if (!list_empty(cache)) { + new = list_entry(cache->next, struct ordered_event, list); + list_del(&new->list); + } else if (oe->buffer) { + new = oe->buffer + oe->buffer_idx; + if (++oe->buffer_idx == MAX_SAMPLE_BUFFER) + oe->buffer = NULL; + } else if (oe->cur_alloc_size < oe->max_alloc_size) { + size_t size = MAX_SAMPLE_BUFFER * sizeof(*new); + + oe->buffer = malloc(size); + if (!oe->buffer) + return NULL; + + pr("alloc size %" PRIu64 "B (+%zu), max %" PRIu64 "B\n", + oe->cur_alloc_size, size, oe->max_alloc_size); + + oe->cur_alloc_size += size; + list_add(&oe->buffer->list, &oe->to_free); + + /* First entry is abused to maintain the to_free list. */ + oe->buffer_idx = 2; + new = oe->buffer + 1; + } else { + pr("allocation limit reached %" PRIu64 "B\n", oe->max_alloc_size); + } + + return new; +} + +struct ordered_event * +ordered_events__new(struct ordered_events *oe, u64 timestamp) +{ + struct ordered_event *new; + + new = alloc_event(oe); + if (new) { + new->timestamp = timestamp; + queue_event(oe, new); + } + + return new; +} + +void ordered_events__delete(struct ordered_events *oe, struct ordered_event *event) +{ + list_move(&event->list, &oe->cache); + oe->nr_events--; +} + +static int __ordered_events__flush(struct perf_session *s, + struct perf_tool *tool) +{ + struct ordered_events *oe = &s->ordered_events; + struct list_head *head = &oe->events; + struct ordered_event *tmp, *iter; + struct perf_sample sample; + u64 limit = oe->next_flush; + u64 last_ts = oe->last ? oe->last->timestamp : 0ULL; + bool show_progress = limit == ULLONG_MAX; + struct ui_progress prog; + int ret; + + if (!tool->ordered_events || !limit) + return 0; + + if (show_progress) + ui_progress__init(&prog, oe->nr_events, "Processing time ordered events..."); + + list_for_each_entry_safe(iter, tmp, head, list) { + if (session_done()) + return 0; + + if (iter->timestamp > limit) + break; + + ret = perf_evlist__parse_sample(s->evlist, iter->event, &sample); + if (ret) + pr_err("Can't parse sample, err = %d\n", ret); + else { + ret = perf_session__deliver_event(s, iter->event, &sample, tool, + iter->file_offset); + if (ret) + return ret; + } + + ordered_events__delete(oe, iter); + oe->last_flush = iter->timestamp; + + if (show_progress) + ui_progress__update(&prog, 1); + } + + if (list_empty(head)) + oe->last = NULL; + else if (last_ts <= limit) + oe->last = list_entry(head->prev, struct ordered_event, list); + + return 0; +} + +int ordered_events__flush(struct perf_session *s, struct perf_tool *tool, + enum oe_flush how) +{ + struct ordered_events *oe = &s->ordered_events; + static const char * const str[] = { + "NONE", + "FINAL", + "ROUND", + "HALF ", + }; + int err; + + switch (how) { + case OE_FLUSH__FINAL: + oe->next_flush = ULLONG_MAX; + break; + + case OE_FLUSH__HALF: + { + struct ordered_event *first, *last; + struct list_head *head = &oe->events; + + first = list_entry(head->next, struct ordered_event, list); + last = oe->last; + + /* Warn if we are called before any event got allocated. */ + if (WARN_ONCE(!last || list_empty(head), "empty queue")) + return 0; + + oe->next_flush = first->timestamp; + oe->next_flush += (last->timestamp - first->timestamp) / 2; + break; + } + + case OE_FLUSH__ROUND: + case OE_FLUSH__NONE: + default: + break; + }; + + pr_oe_time(oe->next_flush, "next_flush - ordered_events__flush PRE %s, nr_events %u\n", + str[how], oe->nr_events); + pr_oe_time(oe->max_timestamp, "max_timestamp\n"); + + err = __ordered_events__flush(s, tool); + + if (!err) { + if (how == OE_FLUSH__ROUND) + oe->next_flush = oe->max_timestamp; + + oe->last_flush_type = how; + } + + pr_oe_time(oe->next_flush, "next_flush - ordered_events__flush POST %s, nr_events %u\n", + str[how], oe->nr_events); + pr_oe_time(oe->last_flush, "last_flush\n"); + + return err; +} + +void ordered_events__init(struct ordered_events *oe) +{ + INIT_LIST_HEAD(&oe->events); + INIT_LIST_HEAD(&oe->cache); + INIT_LIST_HEAD(&oe->to_free); + oe->max_alloc_size = (u64) -1; + oe->cur_alloc_size = 0; +} + +void ordered_events__free(struct ordered_events *oe) +{ + while (!list_empty(&oe->to_free)) { + struct ordered_event *event; + + event = list_entry(oe->to_free.next, struct ordered_event, list); + list_del(&event->list); + free(event); + } +} diff --git a/tools/perf/util/ordered-events.h b/tools/perf/util/ordered-events.h new file mode 100644 index 000000000000..3b2f20542a01 --- /dev/null +++ b/tools/perf/util/ordered-events.h @@ -0,0 +1,51 @@ +#ifndef __ORDERED_EVENTS_H +#define __ORDERED_EVENTS_H + +#include <linux/types.h> +#include "tool.h" + +struct perf_session; + +struct ordered_event { + u64 timestamp; + u64 file_offset; + union perf_event *event; + struct list_head list; +}; + +enum oe_flush { + OE_FLUSH__NONE, + OE_FLUSH__FINAL, + OE_FLUSH__ROUND, + OE_FLUSH__HALF, +}; + +struct ordered_events { + u64 last_flush; + u64 next_flush; + u64 max_timestamp; + u64 max_alloc_size; + u64 cur_alloc_size; + struct list_head events; + struct list_head cache; + struct list_head to_free; + struct ordered_event *buffer; + struct ordered_event *last; + int buffer_idx; + unsigned int nr_events; + enum oe_flush last_flush_type; +}; + +struct ordered_event *ordered_events__new(struct ordered_events *oe, u64 timestamp); +void ordered_events__delete(struct ordered_events *oe, struct ordered_event *event); +int ordered_events__flush(struct perf_session *s, struct perf_tool *tool, + enum oe_flush how); +void ordered_events__init(struct ordered_events *oe); +void ordered_events__free(struct ordered_events *oe); + +static inline +void ordered_events__set_alloc_size(struct ordered_events *oe, u64 size) +{ + oe->max_alloc_size = size; +} +#endif /* __ORDERED_EVENTS_H */ diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 1e15df10a88c..d76aa30cb1fb 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -10,6 +10,7 @@ #include "symbol.h" #include "cache.h" #include "header.h" +#include "debug.h" #include <api/fs/debugfs.h> #include "parse-events-bison.h" #define YY_EXTRA_TYPE int @@ -633,18 +634,28 @@ int parse_events_add_pmu(struct list_head *list, int *idx, char *name, struct list_head *head_config) { struct perf_event_attr attr; + struct perf_pmu_info info; struct perf_pmu *pmu; struct perf_evsel *evsel; - const char *unit; - double scale; pmu = perf_pmu__find(name); if (!pmu) return -EINVAL; - memset(&attr, 0, sizeof(attr)); + if (pmu->default_config) { + memcpy(&attr, pmu->default_config, + sizeof(struct perf_event_attr)); + } else { + memset(&attr, 0, sizeof(attr)); + } + + if (!head_config) { + attr.type = pmu->type; + evsel = __add_event(list, idx, &attr, NULL, pmu->cpus); + return evsel ? 0 : -ENOMEM; + } - if (perf_pmu__check_alias(pmu, head_config, &unit, &scale)) + if (perf_pmu__check_alias(pmu, head_config, &info)) return -EINVAL; /* @@ -659,8 +670,8 @@ int parse_events_add_pmu(struct list_head *list, int *idx, evsel = __add_event(list, idx, &attr, pmu_event_name(head_config), pmu->cpus); if (evsel) { - evsel->unit = unit; - evsel->scale = scale; + evsel->unit = info.unit; + evsel->scale = info.scale; } return evsel ? 0 : -ENOMEM; @@ -973,7 +984,7 @@ int parse_filter(const struct option *opt, const char *str, if (last == NULL || last->attr.type != PERF_TYPE_TRACEPOINT) { fprintf(stderr, - "-F option should follow a -e tracepoint option\n"); + "--filter option should follow a -e tracepoint option\n"); return -1; } @@ -1006,9 +1017,11 @@ void print_tracepoint_events(const char *subsys_glob, const char *event_glob, struct dirent *sys_next, *evt_next, sys_dirent, evt_dirent; char evt_path[MAXPATHLEN]; char dir_path[MAXPATHLEN]; + char sbuf[STRERR_BUFSIZE]; if (debugfs_valid_mountpoint(tracing_events_path)) { - printf(" [ Tracepoints not available: %s ]\n", strerror(errno)); + printf(" [ Tracepoints not available: %s ]\n", + strerror_r(errno, sbuf, sizeof(sbuf))); return; } diff --git a/tools/perf/util/parse-events.y b/tools/perf/util/parse-events.y index 0bc87ba46bf3..55fab6ad609a 100644 --- a/tools/perf/util/parse-events.y +++ b/tools/perf/util/parse-events.y @@ -210,6 +210,16 @@ PE_NAME '/' event_config '/' parse_events__free_terms($3); $$ = list; } +| +PE_NAME '/' '/' +{ + struct parse_events_evlist *data = _data; + struct list_head *list; + + ALLOC_LIST(list); + ABORT_ON(parse_events_add_pmu(list, &data->idx, $1, NULL)); + $$ = list; +} value_sym: PE_VALUE_SYM_HW 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/pmu.c b/tools/perf/util/pmu.c index 7a811eb61f75..93a41ca96b8e 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -2,6 +2,8 @@ #include <sys/types.h> #include <unistd.h> #include <stdio.h> +#include <stdbool.h> +#include <stdarg.h> #include <dirent.h> #include <api/fs/fs.h> #include <locale.h> @@ -14,8 +16,8 @@ struct perf_pmu_alias { char *name; - struct list_head terms; - struct list_head list; + struct list_head terms; /* HEAD struct parse_events_term -> list */ + struct list_head list; /* ELEM */ char unit[UNIT_MAX_LEN+1]; double scale; }; @@ -208,6 +210,19 @@ static int perf_pmu__new_alias(struct list_head *list, char *dir, char *name, FI return 0; } +static inline bool pmu_alias_info_file(char *name) +{ + size_t len; + + len = strlen(name); + if (len > 5 && !strcmp(name + len - 5, ".unit")) + return true; + if (len > 6 && !strcmp(name + len - 6, ".scale")) + return true; + + return false; +} + /* * Process all the sysfs attributes located under the directory * specified in 'dir' parameter. @@ -216,7 +231,6 @@ static int pmu_aliases_parse(char *dir, struct list_head *head) { struct dirent *evt_ent; DIR *event_dir; - size_t len; int ret = 0; event_dir = opendir(dir); @@ -232,13 +246,9 @@ static int pmu_aliases_parse(char *dir, struct list_head *head) continue; /* - * skip .unit and .scale info files - * parsed in perf_pmu__new_alias() + * skip info files parsed in perf_pmu__new_alias() */ - len = strlen(name); - if (len > 5 && !strcmp(name + len - 5, ".unit")) - continue; - if (len > 6 && !strcmp(name + len - 6, ".scale")) + if (pmu_alias_info_file(name)) continue; snprintf(path, PATH_MAX, "%s/%s", dir, name); @@ -387,6 +397,12 @@ static struct cpu_map *pmu_cpumask(const char *name) return cpus; } +struct perf_event_attr *__attribute__((weak)) +perf_pmu__get_default_config(struct perf_pmu *pmu __maybe_unused) +{ + return NULL; +} + static struct perf_pmu *pmu_lookup(const char *name) { struct perf_pmu *pmu; @@ -421,6 +437,9 @@ static struct perf_pmu *pmu_lookup(const char *name) pmu->name = strdup(name); pmu->type = type; list_add_tail(&pmu->list, &pmus); + + pmu->default_config = perf_pmu__get_default_config(pmu); + return pmu; } @@ -479,28 +498,24 @@ pmu_find_format(struct list_head *formats, char *name) } /* - * Returns value based on the format definition (format parameter) + * Sets value based on the format definition (format parameter) * and unformated value (value parameter). - * - * TODO maybe optimize a little ;) */ -static __u64 pmu_format_value(unsigned long *format, __u64 value) +static void pmu_format_value(unsigned long *format, __u64 value, __u64 *v, + bool zero) { unsigned long fbit, vbit; - __u64 v = 0; for (fbit = 0, vbit = 0; fbit < PERF_PMU_FORMAT_BITS; fbit++) { if (!test_bit(fbit, format)) continue; - if (!(value & (1llu << vbit++))) - continue; - - v |= (1llu << fbit); + if (value & (1llu << vbit++)) + *v |= (1llu << fbit); + else if (zero) + *v &= ~(1llu << fbit); } - - return v; } /* @@ -509,7 +524,8 @@ static __u64 pmu_format_value(unsigned long *format, __u64 value) */ static int pmu_config_term(struct list_head *formats, struct perf_event_attr *attr, - struct parse_events_term *term) + struct parse_events_term *term, + bool zero) { struct perf_pmu_format *format; __u64 *vp; @@ -548,18 +564,19 @@ static int pmu_config_term(struct list_head *formats, * non-hardcoded terms, here's the place to translate * them into value. */ - *vp |= pmu_format_value(format->bits, term->val.num); + pmu_format_value(format->bits, term->val.num, vp, zero); return 0; } int perf_pmu__config_terms(struct list_head *formats, struct perf_event_attr *attr, - struct list_head *head_terms) + struct list_head *head_terms, + bool zero) { struct parse_events_term *term; list_for_each_entry(term, head_terms, list) - if (pmu_config_term(formats, attr, term)) + if (pmu_config_term(formats, attr, term, zero)) return -EINVAL; return 0; @@ -573,8 +590,10 @@ int perf_pmu__config_terms(struct list_head *formats, int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr, struct list_head *head_terms) { + bool zero = !!pmu->default_config; + attr->type = pmu->type; - return perf_pmu__config_terms(&pmu->format, attr, head_terms); + return perf_pmu__config_terms(&pmu->format, attr, head_terms, zero); } static struct perf_pmu_alias *pmu_find_alias(struct perf_pmu *pmu, @@ -634,7 +653,7 @@ static int check_unit_scale(struct perf_pmu_alias *alias, * defined for the alias */ int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms, - const char **unit, double *scale) + struct perf_pmu_info *info) { struct parse_events_term *term, *h; struct perf_pmu_alias *alias; @@ -644,8 +663,8 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms, * Mark unit and scale as not set * (different from default values, see below) */ - *unit = NULL; - *scale = 0.0; + info->unit = NULL; + info->scale = 0.0; list_for_each_entry_safe(term, h, head_terms, list) { alias = pmu_find_alias(pmu, term); @@ -655,7 +674,7 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms, if (ret) return ret; - ret = check_unit_scale(alias, unit, scale); + ret = check_unit_scale(alias, &info->unit, &info->scale); if (ret) return ret; @@ -668,11 +687,11 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms, * set defaults as for evsel * unit cannot left to NULL */ - if (*unit == NULL) - *unit = ""; + if (info->unit == NULL) + info->unit = ""; - if (*scale == 0.0) - *scale = 1.0; + if (info->scale == 0.0) + info->scale = 1.0; return 0; } @@ -794,3 +813,39 @@ bool pmu_have_event(const char *pname, const char *name) } return false; } + +static FILE *perf_pmu__open_file(struct perf_pmu *pmu, const char *name) +{ + struct stat st; + char path[PATH_MAX]; + const char *sysfs; + + sysfs = sysfs__mountpoint(); + if (!sysfs) + return NULL; + + snprintf(path, PATH_MAX, + "%s" EVENT_SOURCE_DEVICE_PATH "%s/%s", sysfs, pmu->name, name); + + if (stat(path, &st) < 0) + return NULL; + + return fopen(path, "r"); +} + +int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt, + ...) +{ + va_list args; + FILE *file; + int ret = EOF; + + va_start(args, fmt); + file = perf_pmu__open_file(pmu, name); + if (file) { + ret = vfscanf(file, fmt, args); + fclose(file); + } + va_end(args); + return ret; +} diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index c14a543ce1f3..fe90a012c003 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -13,13 +13,21 @@ enum { #define PERF_PMU_FORMAT_BITS 64 +struct perf_event_attr; + struct perf_pmu { char *name; __u32 type; + struct perf_event_attr *default_config; struct cpu_map *cpus; - struct list_head format; - struct list_head aliases; - struct list_head list; + struct list_head format; /* HEAD struct perf_pmu_format -> list */ + struct list_head aliases; /* HEAD struct perf_pmu_alias -> list */ + struct list_head list; /* ELEM */ +}; + +struct perf_pmu_info { + const char *unit; + double scale; }; struct perf_pmu *perf_pmu__find(const char *name); @@ -27,9 +35,10 @@ int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr, struct list_head *head_terms); int perf_pmu__config_terms(struct list_head *formats, struct perf_event_attr *attr, - struct list_head *head_terms); + struct list_head *head_terms, + bool zero); int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms, - const char **unit, double *scale); + struct perf_pmu_info *info); struct list_head *perf_pmu__alias(struct perf_pmu *pmu, struct list_head *head_terms); int perf_pmu_wrap(void); @@ -45,5 +54,11 @@ struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu); void print_pmu_events(const char *event_glob, bool name_only); bool pmu_have_event(const char *pname, const char *name); +int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt, + ...) __attribute__((format(scanf, 3, 4))); + int perf_pmu__test(void); + +struct perf_event_attr *perf_pmu__get_default_config(struct perf_pmu *pmu); + #endif /* __PMU_H */ diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 9a0a1839a377..c150ca4343eb 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -79,7 +79,7 @@ static int init_symbol_maps(bool user_only) int ret; symbol_conf.sort_by_name = true; - ret = symbol__init(); + ret = symbol__init(NULL); if (ret < 0) { pr_debug("Failed to init symbol map.\n"); goto out; @@ -184,7 +184,8 @@ static struct dso *kernel_get_module_dso(const char *module) const char *vmlinux_name; if (module) { - list_for_each_entry(dso, &host_machine->kernel_dsos, node) { + list_for_each_entry(dso, &host_machine->kernel_dsos.head, + node) { if (strncmp(dso->short_name + 1, module, dso->short_name_len - 2) == 0) goto found; @@ -258,21 +259,33 @@ static void clear_probe_trace_events(struct probe_trace_event *tevs, int ntevs) #ifdef HAVE_DWARF_SUPPORT /* Open new debuginfo of given module */ -static struct debuginfo *open_debuginfo(const char *module) +static struct debuginfo *open_debuginfo(const char *module, bool silent) { const char *path = module; + struct debuginfo *ret; if (!module || !strchr(module, '/')) { path = kernel_get_module_path(module); if (!path) { - pr_err("Failed to find path of %s module.\n", - module ?: "kernel"); + if (!silent) + pr_err("Failed to find path of %s module.\n", + module ?: "kernel"); return NULL; } } - return debuginfo__new(path); + ret = debuginfo__new(path); + if (!ret && !silent) { + pr_warning("The %s file has no debug information.\n", path); + if (!module || !strtailcmp(path, ".ko")) + pr_warning("Rebuild with CONFIG_DEBUG_INFO=y, "); + else + pr_warning("Rebuild with -g, "); + pr_warning("or install an appropriate debuginfo package.\n"); + } + return ret; } + static int get_text_start_address(const char *exec, unsigned long *address) { Elf *elf; @@ -333,15 +346,13 @@ static int find_perf_probe_point_from_dwarf(struct probe_trace_point *tp, pr_debug("try to find information at %" PRIx64 " in %s\n", addr, tp->module ? : "kernel"); - dinfo = open_debuginfo(tp->module); + dinfo = open_debuginfo(tp->module, verbose == 0); if (dinfo) { ret = debuginfo__find_probe_point(dinfo, (unsigned long)addr, pp); debuginfo__delete(dinfo); - } else { - pr_debug("Failed to open debuginfo at 0x%" PRIx64 "\n", addr); + } else ret = -ENOENT; - } if (ret > 0) { pp->retprobe = tp->retprobe; @@ -457,13 +468,11 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, struct debuginfo *dinfo; int ntevs, ret = 0; - dinfo = open_debuginfo(target); + dinfo = open_debuginfo(target, !need_dwarf); if (!dinfo) { - if (need_dwarf) { - pr_warning("Failed to open debuginfo file.\n"); + if (need_dwarf) return -ENOENT; - } pr_debug("Could not open debuginfo. Try to use symbols.\n"); return 0; } @@ -565,7 +574,7 @@ static int get_real_path(const char *raw_path, const char *comp_dir, static int __show_one_line(FILE *fp, int l, bool skip, bool show_num) { - char buf[LINEBUF_SIZE]; + char buf[LINEBUF_SIZE], sbuf[STRERR_BUFSIZE]; const char *color = show_num ? "" : PERF_COLOR_BLUE; const char *prefix = NULL; @@ -585,7 +594,8 @@ static int __show_one_line(FILE *fp, int l, bool skip, bool show_num) return 1; error: if (ferror(fp)) { - pr_warning("File read error: %s\n", strerror(errno)); + pr_warning("File read error: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); return -1; } return 0; @@ -618,13 +628,12 @@ static int __show_line_range(struct line_range *lr, const char *module) FILE *fp; int ret; char *tmp; + char sbuf[STRERR_BUFSIZE]; /* Search a line range */ - dinfo = open_debuginfo(module); - if (!dinfo) { - pr_warning("Failed to open debuginfo file.\n"); + dinfo = open_debuginfo(module, false); + if (!dinfo) return -ENOENT; - } ret = debuginfo__find_line_range(dinfo, lr); debuginfo__delete(dinfo); @@ -656,7 +665,7 @@ static int __show_line_range(struct line_range *lr, const char *module) fp = fopen(lr->path, "r"); if (fp == NULL) { pr_warning("Failed to open %s: %s\n", lr->path, - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); return -errno; } /* Skip to starting line number */ @@ -689,11 +698,11 @@ end: return ret; } -int show_line_range(struct line_range *lr, const char *module) +int show_line_range(struct line_range *lr, const char *module, bool user) { int ret; - ret = init_symbol_maps(false); + ret = init_symbol_maps(user); if (ret < 0) return ret; ret = __show_line_range(lr, module); @@ -768,13 +777,12 @@ int show_available_vars(struct perf_probe_event *pevs, int npevs, int i, ret = 0; struct debuginfo *dinfo; - ret = init_symbol_maps(false); + ret = init_symbol_maps(pevs->uprobes); if (ret < 0) return ret; - dinfo = open_debuginfo(module); + dinfo = open_debuginfo(module, false); if (!dinfo) { - pr_warning("Failed to open debuginfo file.\n"); ret = -ENOENT; goto out; } @@ -815,7 +823,8 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, } int show_line_range(struct line_range *lr __maybe_unused, - const char *module __maybe_unused) + const char *module __maybe_unused, + bool user __maybe_unused) { pr_warning("Debuginfo-analysis is not supported.\n"); return -ENOSYS; @@ -1405,8 +1414,7 @@ int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, size_t len) return tmp - buf; error: - pr_debug("Failed to synthesize perf probe argument: %s\n", - strerror(-ret)); + pr_debug("Failed to synthesize perf probe argument: %d\n", ret); return ret; } @@ -1455,8 +1463,7 @@ static char *synthesize_perf_probe_point(struct perf_probe_point *pp) return buf; error: - pr_debug("Failed to synthesize perf probe point: %s\n", - strerror(-ret)); + pr_debug("Failed to synthesize perf probe point: %d\n", ret); free(buf); return NULL; } @@ -1780,10 +1787,11 @@ static void clear_probe_trace_event(struct probe_trace_event *tev) memset(tev, 0, sizeof(*tev)); } -static void print_warn_msg(const char *file, bool is_kprobe) +static void print_open_warning(int err, bool is_kprobe) { + char sbuf[STRERR_BUFSIZE]; - if (errno == ENOENT) { + if (err == -ENOENT) { const char *config; if (!is_kprobe) @@ -1791,25 +1799,43 @@ static void print_warn_msg(const char *file, bool is_kprobe) else config = "CONFIG_KPROBE_EVENTS"; - pr_warning("%s file does not exist - please rebuild kernel" - " with %s.\n", file, config); - } else - pr_warning("Failed to open %s file: %s\n", file, - strerror(errno)); + pr_warning("%cprobe_events file does not exist" + " - please rebuild kernel with %s.\n", + is_kprobe ? 'k' : 'u', config); + } else if (err == -ENOTSUP) + pr_warning("Debugfs is not mounted.\n"); + else + pr_warning("Failed to open %cprobe_events: %s\n", + is_kprobe ? 'k' : 'u', + strerror_r(-err, sbuf, sizeof(sbuf))); +} + +static void print_both_open_warning(int kerr, int uerr) +{ + /* Both kprobes and uprobes are disabled, warn it. */ + if (kerr == -ENOTSUP && uerr == -ENOTSUP) + pr_warning("Debugfs is not mounted.\n"); + else if (kerr == -ENOENT && uerr == -ENOENT) + pr_warning("Please rebuild kernel with CONFIG_KPROBE_EVENTS " + "or/and CONFIG_UPROBE_EVENTS.\n"); + else { + char sbuf[STRERR_BUFSIZE]; + pr_warning("Failed to open kprobe events: %s.\n", + strerror_r(-kerr, sbuf, sizeof(sbuf))); + pr_warning("Failed to open uprobe events: %s.\n", + strerror_r(-uerr, sbuf, sizeof(sbuf))); + } } -static int open_probe_events(const char *trace_file, bool readwrite, - bool is_kprobe) +static int open_probe_events(const char *trace_file, bool readwrite) { char buf[PATH_MAX]; const char *__debugfs; int ret; __debugfs = debugfs_find_mountpoint(); - if (__debugfs == NULL) { - pr_warning("Debugfs is not mounted.\n"); - return -ENOENT; - } + if (__debugfs == NULL) + return -ENOTSUP; ret = e_snprintf(buf, PATH_MAX, "%s/%s", __debugfs, trace_file); if (ret >= 0) { @@ -1820,19 +1846,19 @@ static int open_probe_events(const char *trace_file, bool readwrite, ret = open(buf, O_RDONLY, 0); if (ret < 0) - print_warn_msg(buf, is_kprobe); + ret = -errno; } return ret; } static int open_kprobe_events(bool readwrite) { - return open_probe_events("tracing/kprobe_events", readwrite, true); + return open_probe_events("tracing/kprobe_events", readwrite); } static int open_uprobe_events(bool readwrite) { - return open_probe_events("tracing/uprobe_events", readwrite, false); + return open_probe_events("tracing/uprobe_events", readwrite); } /* Get raw string list of current kprobe_events or uprobe_events */ @@ -1857,7 +1883,7 @@ static struct strlist *get_probe_trace_command_rawlist(int fd) p[idx] = '\0'; ret = strlist__add(sl, buf); if (ret < 0) { - pr_debug("strlist__add failed: %s\n", strerror(-ret)); + pr_debug("strlist__add failed (%d)\n", ret); strlist__delete(sl); return NULL; } @@ -1916,7 +1942,7 @@ static int __show_perf_probe_events(int fd, bool is_kprobe) rawlist = get_probe_trace_command_rawlist(fd); if (!rawlist) - return -ENOENT; + return -ENOMEM; strlist__for_each(ent, rawlist) { ret = parse_probe_trace_command(ent->s, &tev); @@ -1940,27 +1966,34 @@ static int __show_perf_probe_events(int fd, bool is_kprobe) /* List up current perf-probe events */ int show_perf_probe_events(void) { - int fd, ret; + int kp_fd, up_fd, ret; setup_pager(); - fd = open_kprobe_events(false); - - if (fd < 0) - return fd; ret = init_symbol_maps(false); if (ret < 0) return ret; - ret = __show_perf_probe_events(fd, true); - close(fd); + kp_fd = open_kprobe_events(false); + if (kp_fd >= 0) { + ret = __show_perf_probe_events(kp_fd, true); + close(kp_fd); + if (ret < 0) + goto out; + } - fd = open_uprobe_events(false); - if (fd >= 0) { - ret = __show_perf_probe_events(fd, false); - close(fd); + up_fd = open_uprobe_events(false); + if (kp_fd < 0 && up_fd < 0) { + print_both_open_warning(kp_fd, up_fd); + ret = kp_fd; + goto out; } + if (up_fd >= 0) { + ret = __show_perf_probe_events(up_fd, false); + close(up_fd); + } +out: exit_symbol_maps(); return ret; } @@ -1976,6 +2009,8 @@ static struct strlist *get_probe_trace_event_names(int fd, bool include_group) memset(&tev, 0, sizeof(tev)); rawlist = get_probe_trace_command_rawlist(fd); + if (!rawlist) + return NULL; sl = strlist__new(true, NULL); strlist__for_each(ent, rawlist) { ret = parse_probe_trace_command(ent->s, &tev); @@ -2005,6 +2040,7 @@ static int write_probe_trace_event(int fd, struct probe_trace_event *tev) { int ret = 0; char *buf = synthesize_probe_trace_command(tev); + char sbuf[STRERR_BUFSIZE]; if (!buf) { pr_debug("Failed to synthesize probe trace event.\n"); @@ -2016,7 +2052,7 @@ static int write_probe_trace_event(int fd, struct probe_trace_event *tev) ret = write(fd, buf, strlen(buf)); if (ret <= 0) pr_warning("Failed to write event: %s\n", - strerror(errno)); + strerror_r(errno, sbuf, sizeof(sbuf))); } free(buf); return ret; @@ -2030,7 +2066,7 @@ static int get_new_event_name(char *buf, size_t len, const char *base, /* Try no suffix */ ret = e_snprintf(buf, len, "%s", base); if (ret < 0) { - pr_debug("snprintf() failed: %s\n", strerror(-ret)); + pr_debug("snprintf() failed: %d\n", ret); return ret; } if (!strlist__has_entry(namelist, buf)) @@ -2046,7 +2082,7 @@ static int get_new_event_name(char *buf, size_t len, const char *base, for (i = 1; i < MAX_EVENT_INDEX; i++) { ret = e_snprintf(buf, len, "%s_%d", base, i); if (ret < 0) { - pr_debug("snprintf() failed: %s\n", strerror(-ret)); + pr_debug("snprintf() failed: %d\n", ret); return ret; } if (!strlist__has_entry(namelist, buf)) @@ -2075,8 +2111,11 @@ static int __add_probe_trace_events(struct perf_probe_event *pev, else fd = open_kprobe_events(true); - if (fd < 0) + if (fd < 0) { + print_open_warning(fd, !pev->uprobes); return fd; + } + /* Get current event names */ namelist = get_probe_trace_event_names(fd, false); if (!namelist) { @@ -2408,7 +2447,8 @@ static int __del_trace_probe_event(int fd, struct str_node *ent) printf("Removed event: %s\n", ent->s); return 0; error: - pr_warning("Failed to delete event: %s\n", strerror(-ret)); + pr_warning("Failed to delete event: %s\n", + strerror_r(-ret, buf, sizeof(buf))); return ret; } @@ -2449,15 +2489,18 @@ int del_perf_probe_events(struct strlist *dellist) /* Get current event names */ kfd = open_kprobe_events(true); - if (kfd < 0) - return kfd; + if (kfd >= 0) + namelist = get_probe_trace_event_names(kfd, true); - namelist = get_probe_trace_event_names(kfd, true); ufd = open_uprobe_events(true); - if (ufd >= 0) unamelist = get_probe_trace_event_names(ufd, true); + if (kfd < 0 && ufd < 0) { + print_both_open_warning(kfd, ufd); + goto error; + } + if (namelist == NULL && unamelist == NULL) goto error; diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h index 776c9347a3b6..e01e9943139f 100644 --- a/tools/perf/util/probe-event.h +++ b/tools/perf/util/probe-event.h @@ -128,7 +128,8 @@ extern int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, bool force_add); extern int del_perf_probe_events(struct strlist *dellist); extern int show_perf_probe_events(void); -extern int show_line_range(struct line_range *lr, const char *module); +extern int show_line_range(struct line_range *lr, const char *module, + bool user); extern int show_available_vars(struct perf_probe_event *pevs, int npevs, int max_probe_points, const char *module, struct strfilter *filter, bool externs); diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 98e304766416..c7918f83b300 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> @@ -282,6 +281,7 @@ static int convert_variable_type(Dwarf_Die *vr_die, struct probe_trace_arg_ref **ref_ptr = &tvar->ref; Dwarf_Die type; char buf[16]; + char sbuf[STRERR_BUFSIZE]; int bsize, boffs, total; int ret; @@ -368,7 +368,7 @@ formatted: if (ret >= 16) ret = -E2BIG; pr_warning("Failed to convert variable type: %s\n", - strerror(-ret)); + strerror_r(-ret, sbuf, sizeof(sbuf))); return ret; } tvar->type = strdup(buf); @@ -609,14 +609,18 @@ static int convert_to_trace_point(Dwarf_Die *sp_die, Dwfl_Module *mod, return -EINVAL; } - /* Get an appropriate symbol from symtab */ - symbol = dwfl_module_addrsym(mod, paddr, &sym, NULL); + symbol = dwarf_diename(sp_die); if (!symbol) { - pr_warning("Failed to find symbol at 0x%lx\n", - (unsigned long)paddr); - return -ENOENT; + /* Try to get the symbol name from symtab */ + symbol = dwfl_module_addrsym(mod, paddr, &sym, NULL); + if (!symbol) { + pr_warning("Failed to find symbol at 0x%lx\n", + (unsigned long)paddr); + return -ENOENT; + } + eaddr = sym.st_value; } - tp->offset = (unsigned long)(paddr - sym.st_value); + tp->offset = (unsigned long)(paddr - eaddr); tp->address = (unsigned long)paddr; tp->symbol = strdup(symbol); if (!tp->symbol) @@ -780,10 +784,12 @@ static int find_lazy_match_lines(struct intlist *list, size_t line_len; ssize_t len; int count = 0, linenum = 1; + char sbuf[STRERR_BUFSIZE]; fp = fopen(fname, "r"); if (!fp) { - pr_warning("Failed to open %s: %s\n", fname, strerror(errno)); + pr_warning("Failed to open %s: %s\n", fname, + strerror_r(errno, sbuf, sizeof(sbuf))); return -errno; } 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..3dda85ca50c1 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); @@ -736,7 +736,7 @@ static PyObject *pyrf_evlist__poll(struct pyrf_evlist *pevlist, if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwlist, &timeout)) return NULL; - n = poll(evlist->pollfd, evlist->nr_fds, timeout); + n = perf_evlist__poll(evlist, timeout); if (n < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; @@ -753,9 +753,9 @@ static PyObject *pyrf_evlist__get_pollfd(struct pyrf_evlist *pevlist, PyObject *list = PyList_New(0); int i; - for (i = 0; i < evlist->nr_fds; ++i) { + for (i = 0; i < evlist->pollfd.nr; ++i) { PyObject *file; - FILE *fp = fdopen(evlist->pollfd[i].fd, "r"); + FILE *fp = fdopen(evlist->pollfd.entries[i].fd, "r"); if (fp == NULL) goto free_list; diff --git a/tools/perf/util/record.c b/tools/perf/util/record.c index 049e0a09ccd3..cf69325b985f 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,7 +12,9 @@ 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; + static pid_t pid = -1; evlist = perf_evlist__new(); if (!evlist) @@ -22,14 +25,22 @@ 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); - if (fd < 0) - goto out_delete; + while (1) { + fd = sys_perf_event_open(&evsel->attr, pid, cpu, -1, flags); + if (fd < 0) { + if (pid == -1 && errno == EACCES) { + pid = 0; + continue; + } + goto out_delete; + } + break; + } close(fd); fn(evsel); - fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0); + fd = sys_perf_event_open(&evsel->attr, pid, cpu, -1, flags); if (fd < 0) { if (errno == EINVAL) err = -EINVAL; @@ -45,7 +56,7 @@ out_delete: static bool perf_probe_api(setup_probe_fn_t fn) { - const char *try[] = {"cycles:u", "instructions:u", "cpu-clock", NULL}; + const char *try[] = {"cycles:u", "instructions:u", "cpu-clock:u", NULL}; struct cpu_map *cpus; int cpu, ret, i = 0; @@ -69,15 +80,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 +111,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->tracking && use_comm_exec) + evsel->attr.comm_exec = 1; + } if (evlist->nr_entries > 1) { struct perf_evsel *first = perf_evlist__first(evlist); @@ -183,6 +210,7 @@ bool perf_evlist__can_select_event(struct perf_evlist *evlist, const char *str) struct perf_evsel *evsel; int err, fd, cpu; bool ret = false; + pid_t pid = -1; temp_evlist = perf_evlist__new(); if (!temp_evlist) @@ -203,11 +231,20 @@ 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); - if (fd >= 0) { - close(fd); - ret = true; + while (1) { + fd = sys_perf_event_open(&evsel->attr, pid, cpu, -1, + perf_event_open_cloexec_flag()); + if (fd < 0) { + if (pid == -1 && errno == EACCES) { + pid = 0; + continue; + } + goto out_delete; + } + break; } + close(fd); + ret = true; out_delete: perf_evlist__delete(temp_evlist); diff --git a/tools/perf/util/run-command.c b/tools/perf/util/run-command.c index da8e9b285f51..34622b53e733 100644 --- a/tools/perf/util/run-command.c +++ b/tools/perf/util/run-command.c @@ -1,6 +1,7 @@ #include "cache.h" #include "run-command.h" #include "exec_cmd.h" +#include "debug.h" static inline void close_pair(int fd[2]) { @@ -19,6 +20,7 @@ int start_command(struct child_process *cmd) { int need_in, need_out, need_err; int fdin[2], fdout[2], fderr[2]; + char sbuf[STRERR_BUFSIZE]; /* * In case of errors we must keep the promise to close FDs @@ -99,7 +101,7 @@ int start_command(struct child_process *cmd) if (cmd->dir && chdir(cmd->dir)) die("exec %s: cd to %s failed (%s)", cmd->argv[0], - cmd->dir, strerror(errno)); + cmd->dir, strerror_r(errno, sbuf, sizeof(sbuf))); if (cmd->env) { for (; *cmd->env; cmd->env++) { if (strchr(*cmd->env, '=')) @@ -153,6 +155,8 @@ int start_command(struct child_process *cmd) static int wait_or_whine(pid_t pid) { + char sbuf[STRERR_BUFSIZE]; + for (;;) { int status, code; pid_t waiting = waitpid(pid, &status, 0); @@ -160,7 +164,8 @@ static int wait_or_whine(pid_t pid) if (waiting < 0) { if (errno == EINTR) continue; - error("waitpid failed (%s)", strerror(errno)); + error("waitpid failed (%s)", + strerror_r(errno, sbuf, sizeof(sbuf))); return -ERR_RUN_COMMAND_WAITPID; } if (waiting != pid) diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c index af7da565a750..0a01bac4ce02 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); @@ -431,6 +432,11 @@ error: return err; } +static int perl_flush_script(void) +{ + return 0; +} + /* * Stop trace script */ @@ -632,6 +638,7 @@ static int perl_generate_script(struct pevent *pevent, const char *outfile) struct scripting_ops perl_scripting_ops = { .name = "Perl", .start_script = perl_start_script, + .flush_script = perl_flush_script, .stop_script = perl_stop_script, .process_event = perl_process_event, .generate_script = perl_generate_script, diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index 1c419321f707..56ba07cce549 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(); } /* @@ -67,6 +73,35 @@ static void pydict_set_item_string_decref(PyObject *dict, const char *key, PyObj Py_DECREF(val); } +static PyObject *get_handler(const char *handler_name) +{ + PyObject *handler; + + handler = PyDict_GetItemString(main_dict, handler_name); + if (handler && !PyCallable_Check(handler)) + return NULL; + return handler; +} + +static void call_object(PyObject *handler, PyObject *args, const char *die_msg) +{ + PyObject *retval; + + retval = PyObject_CallObject(handler, args); + if (retval == NULL) + handler_call_die(die_msg); + Py_DECREF(retval); +} + +static void try_call_object(const char *handler_name, PyObject *args) +{ + PyObject *handler; + + handler = get_handler(handler_name); + if (handler) + call_object(handler, args, handler_name); +} + static void define_value(enum print_arg_type field_type, const char *ev_name, const char *field_name, @@ -74,7 +109,7 @@ static void define_value(enum print_arg_type field_type, const char *field_str) { const char *handler_name = "define_flag_value"; - PyObject *handler, *t, *retval; + PyObject *t; unsigned long long value; unsigned n = 0; @@ -92,12 +127,7 @@ static void define_value(enum print_arg_type field_type, PyTuple_SetItem(t, n++, PyInt_FromLong(value)); PyTuple_SetItem(t, n++, PyString_FromString(field_str)); - handler = PyDict_GetItemString(main_dict, handler_name); - if (handler && PyCallable_Check(handler)) { - retval = PyObject_CallObject(handler, t); - if (retval == NULL) - handler_call_die(handler_name); - } + try_call_object(handler_name, t); Py_DECREF(t); } @@ -120,7 +150,7 @@ static void define_field(enum print_arg_type field_type, const char *delim) { const char *handler_name = "define_flag_field"; - PyObject *handler, *t, *retval; + PyObject *t; unsigned n = 0; if (field_type == PRINT_SYMBOL) @@ -138,12 +168,7 @@ static void define_field(enum print_arg_type field_type, if (field_type == PRINT_FLAGS) PyTuple_SetItem(t, n++, PyString_FromString(delim)); - handler = PyDict_GetItemString(main_dict, handler_name); - if (handler && PyCallable_Check(handler)) { - retval = PyObject_CallObject(handler, t); - if (retval == NULL) - handler_call_die(handler_name); - } + try_call_object(handler_name, t); Py_DECREF(t); } @@ -231,15 +256,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, *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; @@ -261,9 +404,7 @@ static void python_process_tracepoint(struct perf_sample *sample, sprintf(handler_name, "%s__%s", event->system, event->name); - handler = PyDict_GetItemString(main_dict, handler_name); - if (handler && !PyCallable_Check(handler)) - handler = NULL; + handler = get_handler(handler_name); if (!handler) { dict = PyDict_New(); if (!dict) @@ -280,18 +421,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) { @@ -303,20 +449,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); @@ -324,6 +457,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); @@ -331,17 +465,9 @@ static void python_process_tracepoint(struct perf_sample *sample, Py_FatalError("error resizing Python tuple"); if (handler) { - retval = PyObject_CallObject(handler, t); - if (retval == NULL) - handler_call_die(handler_name); + call_object(handler, t, handler_name); } else { - handler = PyDict_GetItemString(main_dict, "trace_unhandled"); - if (handler && PyCallable_Check(handler)) { - - retval = PyObject_CallObject(handler, t); - if (retval == NULL) - handler_call_die("trace_unhandled"); - } + try_call_object("trace_unhandled", t); Py_DECREF(dict); } @@ -353,7 +479,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, *t, *dict, *callchain, *dict_sample; static char handler_name[64]; unsigned n = 0; @@ -369,17 +495,34 @@ 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); - if (!handler || !PyCallable_Check(handler)) + handler = get_handler(handler_name); + if (!handler) goto exit; 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", @@ -393,13 +536,15 @@ 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"); - retval = PyObject_CallObject(handler, t); - if (retval == NULL) - handler_call_die(handler_name); + call_object(handler, t, handler_name); exit: Py_DECREF(dict); Py_DECREF(t); @@ -423,36 +568,24 @@ static void python_process_event(union perf_event *event __maybe_unused, static int run_start_sub(void) { - PyObject *handler, *retval; - int err = 0; - main_module = PyImport_AddModule("__main__"); if (main_module == NULL) return -1; Py_INCREF(main_module); main_dict = PyModule_GetDict(main_module); - if (main_dict == NULL) { - err = -1; + if (main_dict == NULL) goto error; - } Py_INCREF(main_dict); - handler = PyDict_GetItemString(main_dict, "trace_begin"); - if (handler == NULL || !PyCallable_Check(handler)) - goto out; + try_call_object("trace_begin", NULL); - retval = PyObject_CallObject(handler, NULL); - if (retval == NULL) - handler_call_die("trace_begin"); + return 0; - Py_DECREF(retval); - return err; error: Py_XDECREF(main_dict); Py_XDECREF(main_module); -out: - return err; + return -1; } /* @@ -506,29 +639,23 @@ error: return err; } +static int python_flush_script(void) +{ + return 0; +} + /* * Stop trace script */ static int python_stop_script(void) { - PyObject *handler, *retval; - int err = 0; - - handler = PyDict_GetItemString(main_dict, "trace_end"); - if (handler == NULL || !PyCallable_Check(handler)) - goto out; + try_call_object("trace_end", NULL); - retval = PyObject_CallObject(handler, NULL); - if (retval == NULL) - handler_call_die("trace_end"); - else - Py_DECREF(retval); -out: Py_XDECREF(main_dict); Py_XDECREF(main_module); Py_Finalize(); - return err; + return 0; } static int python_generate_script(struct pevent *pevent, const char *outfile) @@ -589,6 +716,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; @@ -632,7 +760,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; @@ -668,7 +796,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, " @@ -692,6 +828,7 @@ static int python_generate_script(struct pevent *pevent, const char *outfile) struct scripting_ops python_scripting_ops = { .name = "Python", .start_script = python_start_script, + .flush_script = python_flush_script, .stop_script = python_stop_script, .process_event = python_process_event, .generate_script = python_generate_script, diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 64a186edc7be..883406f4b381 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -14,7 +14,7 @@ #include "util.h" #include "cpumap.h" #include "perf_regs.h" -#include "vdso.h" +#include "asm/bug.h" static int perf_session__open(struct perf_session *session) { @@ -67,6 +67,25 @@ static void perf_session__destroy_kernel_maps(struct perf_session *session) machines__destroy_kernel_maps(&session->machines); } +static bool perf_session__has_comm_exec(struct perf_session *session) +{ + struct perf_evsel *evsel; + + evlist__for_each(session->evlist, evsel) { + if (evsel->attr.comm_exec) + return true; + } + + return false; +} + +static void perf_session__set_comm_exec(struct perf_session *session) +{ + bool comm_exec = perf_session__has_comm_exec(session); + + machines__set_comm_exec(&session->machines, comm_exec); +} + struct perf_session *perf_session__new(struct perf_data_file *file, bool repipe, struct perf_tool *tool) { @@ -76,9 +95,7 @@ struct perf_session *perf_session__new(struct perf_data_file *file, goto out; session->repipe = repipe; - INIT_LIST_HEAD(&session->ordered_samples.samples); - INIT_LIST_HEAD(&session->ordered_samples.sample_cache); - INIT_LIST_HEAD(&session->ordered_samples.to_free); + ordered_events__init(&session->ordered_events); machines__init(&session->machines); if (file) { @@ -92,6 +109,7 @@ struct perf_session *perf_session__new(struct perf_data_file *file, goto out_close; perf_session__set_id_hdr_size(session); + perf_session__set_comm_exec(session); } } @@ -101,13 +119,13 @@ struct perf_session *perf_session__new(struct perf_data_file *file, * kernel MMAP event, in perf_event__process_mmap(). */ if (perf_session__create_kernel_maps(session) < 0) - goto out_delete; + pr_warning("Cannot read kernel map\n"); } if (tool && tool->ordering_requires_timestamps && - tool->ordered_samples && !perf_evlist__sample_id_all(session->evlist)) { + tool->ordered_events && !perf_evlist__sample_id_all(session->evlist)) { dump_printf("WARNING: No sample_id_all support, falling back to unordered processing\n"); - tool->ordered_samples = false; + tool->ordered_events = false; } return session; @@ -156,7 +174,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 @@ -240,7 +257,7 @@ void perf_tool__fill_defaults(struct perf_tool *tool) if (tool->build_id == NULL) tool->build_id = process_finished_round_stub; if (tool->finished_round == NULL) { - if (tool->ordered_samples) + if (tool->ordered_events) tool->finished_round = process_finished_round; else tool->finished_round = process_finished_round_stub; @@ -446,88 +463,6 @@ static perf_event__swap_op perf_event__swap_ops[] = { [PERF_RECORD_HEADER_MAX] = NULL, }; -struct sample_queue { - u64 timestamp; - u64 file_offset; - union perf_event *event; - struct list_head list; -}; - -static void perf_session_free_sample_buffers(struct perf_session *session) -{ - struct ordered_samples *os = &session->ordered_samples; - - while (!list_empty(&os->to_free)) { - struct sample_queue *sq; - - sq = list_entry(os->to_free.next, struct sample_queue, list); - list_del(&sq->list); - free(sq); - } -} - -static int perf_session_deliver_event(struct perf_session *session, - union perf_event *event, - struct perf_sample *sample, - struct perf_tool *tool, - u64 file_offset); - -static int flush_sample_queue(struct perf_session *s, - struct perf_tool *tool) -{ - struct ordered_samples *os = &s->ordered_samples; - struct list_head *head = &os->samples; - struct sample_queue *tmp, *iter; - struct perf_sample sample; - u64 limit = os->next_flush; - u64 last_ts = os->last_sample ? os->last_sample->timestamp : 0ULL; - bool show_progress = limit == ULLONG_MAX; - struct ui_progress prog; - int ret; - - if (!tool->ordered_samples || !limit) - return 0; - - if (show_progress) - ui_progress__init(&prog, os->nr_samples, "Processing time ordered events..."); - - list_for_each_entry_safe(iter, tmp, head, list) { - if (session_done()) - return 0; - - if (iter->timestamp > limit) - break; - - ret = perf_evlist__parse_sample(s->evlist, iter->event, &sample); - if (ret) - pr_err("Can't parse sample, err = %d\n", ret); - else { - ret = perf_session_deliver_event(s, iter->event, &sample, tool, - iter->file_offset); - if (ret) - return ret; - } - - os->last_flush = iter->timestamp; - list_del(&iter->list); - list_add(&iter->list, &os->sample_cache); - - if (show_progress) - ui_progress__update(&prog, 1); - } - - if (list_empty(head)) { - os->last_sample = NULL; - } else if (last_ts <= limit) { - os->last_sample = - list_entry(head->prev, struct sample_queue, list); - } - - os->nr_samples = 0; - - return 0; -} - /* * When perf record finishes a pass on every buffers, it records this pseudo * event. @@ -571,99 +506,43 @@ static int process_finished_round(struct perf_tool *tool, union perf_event *event __maybe_unused, struct perf_session *session) { - int ret = flush_sample_queue(session, tool); - if (!ret) - session->ordered_samples.next_flush = session->ordered_samples.max_timestamp; - - return ret; -} - -/* The queue is ordered by time */ -static void __queue_event(struct sample_queue *new, struct perf_session *s) -{ - struct ordered_samples *os = &s->ordered_samples; - struct sample_queue *sample = os->last_sample; - u64 timestamp = new->timestamp; - struct list_head *p; - - ++os->nr_samples; - os->last_sample = new; - - if (!sample) { - list_add(&new->list, &os->samples); - os->max_timestamp = timestamp; - return; - } - - /* - * last_sample might point to some random place in the list as it's - * the last queued event. We expect that the new event is close to - * this. - */ - if (sample->timestamp <= timestamp) { - while (sample->timestamp <= timestamp) { - p = sample->list.next; - if (p == &os->samples) { - list_add_tail(&new->list, &os->samples); - os->max_timestamp = timestamp; - return; - } - sample = list_entry(p, struct sample_queue, list); - } - list_add_tail(&new->list, &sample->list); - } else { - while (sample->timestamp > timestamp) { - p = sample->list.prev; - if (p == &os->samples) { - list_add(&new->list, &os->samples); - return; - } - sample = list_entry(p, struct sample_queue, list); - } - list_add(&new->list, &sample->list); - } + return ordered_events__flush(session, tool, OE_FLUSH__ROUND); } -#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct sample_queue)) - int perf_session_queue_event(struct perf_session *s, union perf_event *event, - struct perf_sample *sample, u64 file_offset) + struct perf_tool *tool, struct perf_sample *sample, + u64 file_offset) { - struct ordered_samples *os = &s->ordered_samples; - struct list_head *sc = &os->sample_cache; + struct ordered_events *oe = &s->ordered_events; u64 timestamp = sample->time; - struct sample_queue *new; + struct ordered_event *new; if (!timestamp || timestamp == ~0ULL) return -ETIME; - if (timestamp < s->ordered_samples.last_flush) { - printf("Warning: Timestamp below last timeslice flush\n"); - return -EINVAL; + if (timestamp < oe->last_flush) { + WARN_ONCE(1, "Timestamp below last timeslice flush\n"); + + pr_oe_time(timestamp, "out of order event"); + pr_oe_time(oe->last_flush, "last flush, last_flush_type %d\n", + oe->last_flush_type); + + /* We could get out of order messages after forced flush. */ + if (oe->last_flush_type != OE_FLUSH__HALF) + return -EINVAL; } - if (!list_empty(sc)) { - new = list_entry(sc->next, struct sample_queue, list); - list_del(&new->list); - } else if (os->sample_buffer) { - new = os->sample_buffer + os->sample_buffer_idx; - if (++os->sample_buffer_idx == MAX_SAMPLE_BUFFER) - os->sample_buffer = NULL; - } else { - os->sample_buffer = malloc(MAX_SAMPLE_BUFFER * sizeof(*new)); - if (!os->sample_buffer) - return -ENOMEM; - list_add(&os->sample_buffer->list, &os->to_free); - os->sample_buffer_idx = 2; - new = os->sample_buffer + 1; + new = ordered_events__new(oe, timestamp); + if (!new) { + ordered_events__flush(s, tool, OE_FLUSH__HALF); + new = ordered_events__new(oe, timestamp); } - new->timestamp = timestamp; + if (!new) + return -ENOMEM; + new->file_offset = file_offset; new->event = event; - - __queue_event(new, s); - return 0; } @@ -923,11 +802,10 @@ perf_session__deliver_sample(struct perf_session *session, &sample->read.one, machine); } -static int perf_session_deliver_event(struct perf_session *session, - union perf_event *event, - struct perf_sample *sample, - struct perf_tool *tool, - u64 file_offset) +int perf_session__deliver_event(struct perf_session *session, + union perf_event *event, + struct perf_sample *sample, + struct perf_tool *tool, u64 file_offset) { struct perf_evsel *evsel; struct machine *machine; @@ -994,8 +872,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; @@ -1006,8 +886,10 @@ static int perf_session__process_user_event(struct perf_session *session, union switch (event->header.type) { case PERF_RECORD_HEADER_ATTR: err = tool->attr(tool, event, &session->evlist); - if (err == 0) + if (err == 0) { perf_session__set_id_hdr_size(session); + perf_session__set_comm_exec(session); + } return err; case PERF_RECORD_HEADER_EVENT_TYPE: /* @@ -1037,7 +919,62 @@ 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, +int perf_session__peek_event(struct perf_session *session, off_t file_offset, + void *buf, size_t buf_sz, + union perf_event **event_ptr, + struct perf_sample *sample) +{ + union perf_event *event; + size_t hdr_sz, rest; + int fd; + + if (session->one_mmap && !session->header.needs_swap) { + event = file_offset - session->one_mmap_offset + + session->one_mmap_addr; + goto out_parse_sample; + } + + if (perf_data_file__is_pipe(session->file)) + return -1; + + fd = perf_data_file__fd(session->file); + hdr_sz = sizeof(struct perf_event_header); + + if (buf_sz < hdr_sz) + return -1; + + if (lseek(fd, file_offset, SEEK_SET) == (off_t)-1 || + readn(fd, &buf, hdr_sz) != (ssize_t)hdr_sz) + return -1; + + event = (union perf_event *)buf; + + if (session->header.needs_swap) + perf_event_header__bswap(&event->header); + + if (event->header.size < hdr_sz) + return -1; + + rest = event->header.size - hdr_sz; + + if (readn(fd, &buf, rest) != (ssize_t)rest) + return -1; + + if (session->header.needs_swap) + event_swap(event, perf_evlist__sample_id_all(session->evlist)); + +out_parse_sample: + + if (sample && event->header.type < PERF_RECORD_USER_TYPE_START && + perf_evlist__parse_sample(session->evlist, event, sample)) + return -1; + + *event_ptr = event; + + return 0; +} + +static s64 perf_session__process_event(struct perf_session *session, union perf_event *event, struct perf_tool *tool, u64 file_offset) @@ -1063,15 +1000,15 @@ static int perf_session__process_event(struct perf_session *session, if (ret) return ret; - if (tool->ordered_samples) { - ret = perf_session_queue_event(session, event, &sample, + if (tool->ordered_events) { + ret = perf_session_queue_event(session, event, tool, &sample, file_offset); if (ret != -ETIME) return ret; } - return perf_session_deliver_event(session, event, &sample, tool, - file_offset); + return perf_session__deliver_event(session, event, &sample, tool, + file_offset); } void perf_event_header__bswap(struct perf_event_header *hdr) @@ -1083,13 +1020,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 +1085,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; @@ -1222,12 +1160,11 @@ more: goto more; done: /* do the final flush for ordered samples */ - session->ordered_samples.next_flush = ULLONG_MAX; - err = flush_sample_queue(session, tool); + err = ordered_events__flush(session, tool, OE_FLUSH__FINAL); out_err: free(buf); perf_session__warn_about_errors(session, tool); - perf_session_free_sample_buffers(session); + ordered_events__free(&session->ordered_events); return err; } @@ -1276,13 +1213,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 +1233,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 +1258,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 +1280,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 +1289,9 @@ more: goto out_err; } + if (skip) + size += skip; + head += size; file_pos += size; @@ -1358,12 +1305,12 @@ more: out: /* do the final flush for ordered samples */ - session->ordered_samples.next_flush = ULLONG_MAX; - err = flush_sample_queue(session, tool); + err = ordered_events__flush(session, tool, OE_FLUSH__FINAL); out_err: ui_progress__finish(); perf_session__warn_about_errors(session, tool); - perf_session_free_sample_buffers(session); + ordered_events__free(&session->ordered_events); + session->one_mmap = false; return err; } diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index 3140f8ae6148..ffb440462008 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h @@ -9,26 +9,13 @@ #include "symbol.h" #include "thread.h" #include "data.h" +#include "ordered-events.h" #include <linux/rbtree.h> #include <linux/perf_event.h> -struct sample_queue; struct ip_callchain; struct thread; -struct ordered_samples { - u64 last_flush; - u64 next_flush; - u64 max_timestamp; - struct list_head samples; - struct list_head sample_cache; - struct list_head to_free; - struct sample_queue *sample_buffer; - struct sample_queue *last_sample; - int sample_buffer_idx; - unsigned int nr_samples; -}; - struct perf_session { struct perf_header header; struct machines machines; @@ -36,7 +23,10 @@ struct perf_session { struct trace_event tevent; struct events_stats stats; bool repipe; - struct ordered_samples ordered_samples; + bool one_mmap; + void *one_mmap_addr; + u64 one_mmap_offset; + struct ordered_events ordered_events; struct perf_data_file *file; }; @@ -55,6 +45,11 @@ void perf_session__delete(struct perf_session *session); void perf_event_header__bswap(struct perf_event_header *hdr); +int perf_session__peek_event(struct perf_session *session, off_t file_offset, + void *buf, size_t buf_sz, + union perf_event **event_ptr, + struct perf_sample *sample); + int __perf_session__process_events(struct perf_session *session, u64 data_offset, u64 data_size, u64 size, struct perf_tool *tool); @@ -62,10 +57,16 @@ int perf_session__process_events(struct perf_session *session, struct perf_tool *tool); int perf_session_queue_event(struct perf_session *s, union perf_event *event, - struct perf_sample *sample, u64 file_offset); + struct perf_tool *tool, struct perf_sample *sample, + u64 file_offset); void perf_tool__fill_defaults(struct perf_tool *tool); +int perf_session__deliver_event(struct perf_session *session, + union perf_event *event, + struct perf_sample *sample, + struct perf_tool *tool, u64 file_offset); + int perf_session__resolve_callchain(struct perf_session *session, struct perf_evsel *evsel, struct thread *thread, @@ -125,5 +126,5 @@ int __perf_session__set_tracepoints_handlers(struct perf_session *session, extern volatile int session_done; -#define session_done() (*(volatile int *)(&session_done)) +#define session_done() ACCESS_ONCE(session_done) #endif /* __PERF_SESSION_H */ diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 1ec57dd82284..289df9d1e65a 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -70,12 +70,14 @@ static int hist_entry__thread_snprintf(struct hist_entry *he, char *bf, size_t size, unsigned int width) { const char *comm = thread__comm_str(he->thread); - return repsep_snprintf(bf, size, "%*s:%5d", width - 6, - comm ?: "", he->thread->tid); + + width = max(7U, width) - 6; + return repsep_snprintf(bf, size, "%5d:%-*.*s", he->thread->tid, + width, width, comm ?: ""); } struct sort_entry sort_thread = { - .se_header = "Command: Pid", + .se_header = " Pid:Command", .se_cmp = sort__thread_cmp, .se_snprintf = hist_entry__thread_snprintf, .se_width_idx = HISTC_THREAD, @@ -106,7 +108,7 @@ sort__comm_sort(struct hist_entry *left, struct hist_entry *right) static int hist_entry__comm_snprintf(struct hist_entry *he, char *bf, size_t size, unsigned int width) { - return repsep_snprintf(bf, size, "%*s", width, comm__str(he->comm)); + return repsep_snprintf(bf, size, "%-*.*s", width, width, comm__str(he->comm)); } struct sort_entry sort_comm = { @@ -152,10 +154,10 @@ static int _hist_entry__dso_snprintf(struct map *map, char *bf, if (map && map->dso) { const char *dso_name = !verbose ? map->dso->short_name : map->dso->long_name; - return repsep_snprintf(bf, size, "%-*s", width, dso_name); + return repsep_snprintf(bf, size, "%-*.*s", width, width, dso_name); } - return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); + return repsep_snprintf(bf, size, "%-*.*s", width, width, "[unknown]"); } static int hist_entry__dso_snprintf(struct hist_entry *he, char *bf, @@ -257,7 +259,10 @@ static int _hist_entry__sym_snprintf(struct map *map, struct symbol *sym, width - ret, ""); } - return ret; + if (ret > width) + bf[width] = '\0'; + + return width; } static int hist_entry__sym_snprintf(struct hist_entry *he, char *bf, @@ -302,10 +307,9 @@ sort__srcline_cmp(struct hist_entry *left, struct hist_entry *right) } static int hist_entry__srcline_snprintf(struct hist_entry *he, char *bf, - size_t size, - unsigned int width __maybe_unused) + size_t size, unsigned int width) { - return repsep_snprintf(bf, size, "%s", he->srcline); + return repsep_snprintf(bf, size, "%*.*-s", width, width, he->srcline); } struct sort_entry sort_srcline = { @@ -332,7 +336,7 @@ sort__parent_cmp(struct hist_entry *left, struct hist_entry *right) static int hist_entry__parent_snprintf(struct hist_entry *he, char *bf, size_t size, unsigned int width) { - return repsep_snprintf(bf, size, "%-*s", width, + return repsep_snprintf(bf, size, "%-*.*s", width, width, he->parent ? he->parent->name : "[other]"); } @@ -354,7 +358,7 @@ sort__cpu_cmp(struct hist_entry *left, struct hist_entry *right) static int hist_entry__cpu_snprintf(struct hist_entry *he, char *bf, size_t size, unsigned int width) { - return repsep_snprintf(bf, size, "%*d", width, he->cpu); + return repsep_snprintf(bf, size, "%*.*d", width, width, he->cpu); } struct sort_entry sort_cpu = { @@ -484,7 +488,7 @@ static int hist_entry__mispredict_snprintf(struct hist_entry *he, char *bf, else if (he->branch_info->flags.mispred) out = "Y"; - return repsep_snprintf(bf, size, "%-*s", width, out); + return repsep_snprintf(bf, size, "%-*.*s", width, width, out); } /* --sort daddr_sym */ @@ -1194,7 +1198,7 @@ bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) return hse_a->se == hse_b->se; } -void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists) +void perf_hpp__reset_sort_width(struct perf_hpp_fmt *fmt, struct hists *hists) { struct hpp_sort_entry *hse; @@ -1202,20 +1206,21 @@ void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists) 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)); + hists__new_col_len(hists, hse->se->se_width_idx, strlen(fmt->name)); } 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; + size_t len = fmt->user_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); + if (!len) + len = hists__col_len(&evsel->hists, hse->se->se_width_idx); + + return scnprintf(hpp->buf, hpp->size, "%-*.*s", len, len, fmt->name); } static int __sort__hpp_width(struct perf_hpp_fmt *fmt, @@ -1223,20 +1228,26 @@ static int __sort__hpp_width(struct perf_hpp_fmt *fmt, struct perf_evsel *evsel) { struct hpp_sort_entry *hse; + size_t len = fmt->user_len; hse = container_of(fmt, struct hpp_sort_entry, hpp); - return hists__col_len(&evsel->hists, hse->se->se_width_idx); + if (!len) + len = hists__col_len(&evsel->hists, hse->se->se_width_idx); + + return len; } 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; + size_t len = fmt->user_len; hse = container_of(fmt, struct hpp_sort_entry, hpp); - len = hists__col_len(he->hists, hse->se->se_width_idx); + + if (!len) + len = hists__col_len(he->hists, hse->se->se_width_idx); return hse->se->se_snprintf(he, hpp->buf, hpp->size, len); } @@ -1253,6 +1264,7 @@ __sort_dimension__alloc_hpp(struct sort_dimension *sd) } hse->se = sd->entry; + hse->hpp.name = sd->entry->se_header; hse->hpp.header = __sort__hpp_header; hse->hpp.width = __sort__hpp_width; hse->hpp.entry = __sort__hpp_entry; @@ -1265,6 +1277,8 @@ __sort_dimension__alloc_hpp(struct sort_dimension *sd) INIT_LIST_HEAD(&hse->hpp.list); INIT_LIST_HEAD(&hse->hpp.sort_list); hse->hpp.elide = false; + hse->hpp.len = 0; + hse->hpp.user_len = 0; return hse; } @@ -1432,14 +1446,49 @@ static const char *get_default_sort_order(void) return default_sort_orders[sort__mode]; } +static int setup_sort_order(void) +{ + char *new_sort_order; + + /* + * Append '+'-prefixed sort order to the default sort + * order string. + */ + if (!sort_order || is_strict_order(sort_order)) + return 0; + + if (sort_order[1] == '\0') { + error("Invalid --sort key: `+'"); + return -EINVAL; + } + + /* + * We allocate new sort_order string, but we never free it, + * because it's checked over the rest of the code. + */ + if (asprintf(&new_sort_order, "%s,%s", + get_default_sort_order(), sort_order + 1) < 0) { + error("Not enough memory to set up --sort"); + return -ENOMEM; + } + + sort_order = new_sort_order; + return 0; +} + static int __setup_sorting(void) { char *tmp, *tok, *str; - const char *sort_keys = sort_order; + const char *sort_keys; int ret = 0; + ret = setup_sort_order(); + if (ret) + return ret; + + sort_keys = sort_order; if (sort_keys == NULL) { - if (field_order) { + if (is_strict_order(field_order)) { /* * If user specified field order but no sort order, * we'll honor it and not add default sort orders. @@ -1625,23 +1674,36 @@ static void reset_dimensions(void) memory_sort_dimensions[i].taken = 0; } +bool is_strict_order(const char *order) +{ + return order && (*order != '+'); +} + static int __setup_output_field(void) { - char *tmp, *tok, *str; - int ret = 0; + char *tmp, *tok, *str, *strp; + int ret = -EINVAL; if (field_order == NULL) return 0; reset_dimensions(); - str = strdup(field_order); + strp = str = strdup(field_order); if (str == NULL) { error("Not enough memory to setup output fields"); return -ENOMEM; } - for (tok = strtok_r(str, ", ", &tmp); + if (!is_strict_order(field_order)) + strp++; + + if (!strlen(strp)) { + error("Invalid --fields key: `+'"); + goto out; + } + + for (tok = strtok_r(strp, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) { ret = output_field_add(tok); if (ret == -EINVAL) { @@ -1653,6 +1715,7 @@ static int __setup_output_field(void) } } +out: free(str); return ret; } diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 041f0c9cea2b..c03e4ff8beff 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -218,4 +218,5 @@ void perf_hpp__set_elide(int idx, bool elide); int report_parse_ignore_callees_opt(const struct option *opt, const char *arg, int unset); +bool is_strict_order(const char *order); #endif /* __PERF_SORT_H */ diff --git a/tools/perf/util/string.c b/tools/perf/util/string.c index 2553e5b55b89..d87767f76903 100644 --- a/tools/perf/util/string.c +++ b/tools/perf/util/string.c @@ -9,78 +9,48 @@ */ s64 perf_atoll(const char *str) { - unsigned int i; - s64 length = -1, unit = 1; + s64 length; + char *p; + char c; if (!isdigit(str[0])) goto out_err; - for (i = 1; i < strlen(str); i++) { - switch (str[i]) { - case 'B': - case 'b': - break; - case 'K': - if (str[i + 1] != 'B') - goto out_err; - else - goto kilo; - case 'k': - if (str[i + 1] != 'b') - goto out_err; -kilo: - unit = K; - break; - case 'M': - if (str[i + 1] != 'B') - goto out_err; - else - goto mega; - case 'm': - if (str[i + 1] != 'b') + length = strtoll(str, &p, 10); + switch (c = *p++) { + case 'b': case 'B': + if (*p) goto out_err; -mega: - unit = K * K; + case '\0': + return length; + default: + goto out_err; + /* two-letter suffices */ + case 'k': case 'K': + length <<= 10; break; - case 'G': - if (str[i + 1] != 'B') - goto out_err; - else - goto giga; - case 'g': - if (str[i + 1] != 'b') - goto out_err; -giga: - unit = K * K * K; + case 'm': case 'M': + length <<= 20; break; - case 'T': - if (str[i + 1] != 'B') - goto out_err; - else - goto tera; - case 't': - if (str[i + 1] != 'b') - goto out_err; -tera: - unit = K * K * K * K; + case 'g': case 'G': + length <<= 30; break; - case '\0': /* only specified figures */ - unit = 1; + case 't': case 'T': + length <<= 40; break; - default: - if (!isdigit(str[i])) - goto out_err; - break; - } } - - length = atoll(str) * unit; - goto out; + /* we want the cases to match */ + if (islower(c)) { + if (strcmp(p, "b") != 0) + goto out_err; + } else { + if (strcmp(p, "B") != 0) + goto out_err; + } + return length; out_err: - length = -1; -out: - return length; + return -1; } /* diff --git a/tools/perf/util/svghelper.c b/tools/perf/util/svghelper.c index 6a0a13d07a28..283d3e73e2f2 100644 --- a/tools/perf/util/svghelper.c +++ b/tools/perf/util/svghelper.c @@ -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 e3aff5332e30..9292a5291445 100644 --- a/tools/perf/util/svghelper.h +++ b/tools/perf/util/svghelper.h @@ -4,6 +4,9 @@ #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..1e23a5bfb044 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -6,6 +6,7 @@ #include <inttypes.h> #include "symbol.h" +#include "machine.h" #include "vdso.h" #include <symbol/kallsyms.h> #include "debug.h" @@ -49,7 +50,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 +600,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 +623,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); @@ -677,6 +681,11 @@ static u64 ref_reloc(struct kmap *kmap) return 0; } +static bool want_demangle(bool is_kernel_sym) +{ + return is_kernel_sym ? symbol_conf.demangle_kernel : symbol_conf.demangle; +} + int dso__load_sym(struct dso *dso, struct map *map, struct symsrc *syms_ss, struct symsrc *runtime_ss, symbol_filter_t filter, int kmodule) @@ -698,6 +707,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; /* @@ -708,6 +718,14 @@ int dso__load_sym(struct dso *dso, struct map *map, symbols__delete(&dso->symbols[map->type]); if (!syms_ss->symtab) { + /* + * If the vmlinux is stripped, fail so we will fall back + * to using kallsyms. The vmlinux runtime symbols aren't + * of much use. + */ + if (dso->kernel) + goto out_elf_end; + syms_ss->symtab = syms_ss->dynsym; syms_ss->symshdr = syms_ss->dynshdr; } @@ -732,7 +750,7 @@ int dso__load_sym(struct dso *dso, struct map *map, if (symstrs == NULL) goto out_elf_end; - sec_strndx = elf_getscn(elf, ehdr.e_shstrndx); + sec_strndx = elf_getscn(runtime_ss->elf, runtime_ss->ehdr.e_shstrndx); if (sec_strndx == NULL) goto out_elf_end; @@ -912,7 +930,11 @@ int dso__load_sym(struct dso *dso, struct map *map, } curr_dso->symtab_type = dso->symtab_type; map_groups__insert(kmap->kmaps, curr_map); - dsos__add(&dso->node, curr_dso); + /* + * The new DSO should go to the kernel DSOS + */ + dsos__add(&map->groups->machine->kernel_dsos, + curr_dso); dso__set_loaded(curr_dso, map->type); } else curr_dso = curr_map->dso; @@ -934,9 +956,12 @@ new_symbol: * DWARF DW_compile_unit has this, but we don't always have access * to it... */ - if (symbol_conf.demangle) { - demangled = bfd_demangle(NULL, elf_name, - DMGL_PARAMS | DMGL_ANSI); + if (want_demangle(dso->kernel || kmodule)) { + int demangle_flags = DMGL_NO_OPTS; + if (verbose) + demangle_flags = DMGL_PARAMS | DMGL_ANSI; + + demangled = bfd_demangle(NULL, elf_name, demangle_flags); if (demangled != NULL) elf_name = demangled; } @@ -1024,6 +1049,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 7b9096f29cdb..be84f7a9838b 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -15,6 +15,7 @@ #include "machine.h" #include "symbol.h" #include "strlist.h" +#include "header.h" #include <elf.h> #include <limits.h> @@ -33,7 +34,9 @@ struct symbol_conf symbol_conf = { .try_vmlinux_path = true, .annotate_src = true, .demangle = true, + .demangle_kernel = false, .cumulate_callchain = true, + .show_hist_headers = true, .symfs = "", }; @@ -341,6 +344,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; @@ -411,11 +424,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) { @@ -507,10 +525,15 @@ struct process_kallsyms_args { struct dso *dso; }; +/* + * These are symbols in the kernel image, so make sure that + * sym is from a kernel DSO. + */ bool symbol__is_idle(struct symbol *sym) { const char * const idle_symbols[] = { "cpu_idle", + "cpu_startup_entry", "intel_idle", "default_idle", "native_safe_halt", @@ -1064,6 +1087,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; @@ -1451,8 +1475,7 @@ int dso__load_vmlinux(struct dso *dso, struct map *map, if (vmlinux[0] == '/') snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s", vmlinux); else - snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s%s", - symbol_conf.symfs, vmlinux); + symbol__join_symfs(symfs_vmlinux, vmlinux); if (dso->kernel == DSO_TYPE_GUEST_KERNEL) symtab_type = DSO_BINARY_TYPE__GUEST_VMLINUX; @@ -1662,6 +1685,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); @@ -1709,6 +1733,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); @@ -1726,12 +1751,13 @@ static void vmlinux_path__exit(void) zfree(&vmlinux_path); } -static int vmlinux_path__init(void) +static int vmlinux_path__init(struct perf_session_env *env) { struct utsname uts; char bf[PATH_MAX]; + char *kernel_version; - vmlinux_path = malloc(sizeof(char *) * 5); + vmlinux_path = malloc(sizeof(char *) * 6); if (vmlinux_path == NULL) return -1; @@ -1744,25 +1770,37 @@ static int vmlinux_path__init(void) goto out_fail; ++vmlinux_path__nr_entries; - /* only try running kernel version if no symfs was given */ + /* only try kernel version if no symfs was given */ if (symbol_conf.symfs[0] != 0) return 0; - if (uname(&uts) < 0) - return -1; + if (env) { + kernel_version = env->os_release; + } else { + if (uname(&uts) < 0) + goto out_fail; - snprintf(bf, sizeof(bf), "/boot/vmlinux-%s", uts.release); + kernel_version = uts.release; + } + + snprintf(bf, sizeof(bf), "/boot/vmlinux-%s", kernel_version); vmlinux_path[vmlinux_path__nr_entries] = strdup(bf); if (vmlinux_path[vmlinux_path__nr_entries] == NULL) goto out_fail; ++vmlinux_path__nr_entries; - snprintf(bf, sizeof(bf), "/lib/modules/%s/build/vmlinux", uts.release); + snprintf(bf, sizeof(bf), "/usr/lib/debug/boot/vmlinux-%s", + kernel_version); + vmlinux_path[vmlinux_path__nr_entries] = strdup(bf); + if (vmlinux_path[vmlinux_path__nr_entries] == NULL) + goto out_fail; + ++vmlinux_path__nr_entries; + snprintf(bf, sizeof(bf), "/lib/modules/%s/build/vmlinux", kernel_version); vmlinux_path[vmlinux_path__nr_entries] = strdup(bf); if (vmlinux_path[vmlinux_path__nr_entries] == NULL) goto out_fail; ++vmlinux_path__nr_entries; snprintf(bf, sizeof(bf), "/usr/lib/debug/lib/modules/%s/vmlinux", - uts.release); + kernel_version); vmlinux_path[vmlinux_path__nr_entries] = strdup(bf); if (vmlinux_path[vmlinux_path__nr_entries] == NULL) goto out_fail; @@ -1808,7 +1846,7 @@ static bool symbol__read_kptr_restrict(void) return value; } -int symbol__init(void) +int symbol__init(struct perf_session_env *env) { const char *symfs; @@ -1823,7 +1861,7 @@ int symbol__init(void) symbol_conf.priv_size += (sizeof(struct symbol_name_rb_node) - sizeof(struct symbol)); - if (symbol_conf.try_vmlinux_path && vmlinux_path__init() < 0) + if (symbol_conf.try_vmlinux_path && vmlinux_path__init(env) < 0) return -1; if (symbol_conf.field_sep && *symbol_conf.field_sep == '.') { diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 615c752dd767..bec4b7bd09de 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -13,6 +13,7 @@ #include <libgen.h> #include "build-id.h" #include "event.h" +#include "util.h" #ifdef HAVE_LIBELF_SUPPORT #include <libelf.h> @@ -59,6 +60,7 @@ extern Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, #endif #ifndef DMGL_PARAMS +#define DMGL_NO_OPTS 0 /* For readability... */ #define DMGL_PARAMS (1 << 0) /* Include function args */ #define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */ #endif @@ -118,7 +120,9 @@ struct symbol_conf { annotate_src, event_group, demangle, - filter_relative; + demangle_kernel, + filter_relative, + show_hist_headers; const char *vmlinux_name, *kallsyms_name, *source_prefix, @@ -142,6 +146,14 @@ struct symbol_conf { }; extern struct symbol_conf symbol_conf; + +static inline int __symbol__join_symfs(char *bf, size_t size, const char *path) +{ + return path__join(bf, size, symbol_conf.symfs, path); +} + +#define symbol__join_symfs(bf, path) __symbol__join_symfs(bf, sizeof(bf), path) + extern int vmlinux_path__nr_entries; extern char **vmlinux_path; @@ -215,6 +227,7 @@ struct symsrc { GElf_Shdr dynshdr; bool adjust_symbols; + bool is_64_bit; #endif }; @@ -238,6 +251,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, @@ -246,7 +264,8 @@ int modules__parse(const char *filename, void *arg, int filename__read_debuglink(const char *filename, char *debuglink, size_t size); -int symbol__init(void); +struct perf_session_env; +int symbol__init(struct perf_session_env *env); void symbol__exit(void); void symbol__elf_init(void); struct symbol *symbol__new(u64 start, u64 len, u8 binding, const char *name); diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index 2fde0d5e40b5..a9df7f2c6dc9 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -13,7 +13,7 @@ int thread__init_map_groups(struct thread *thread, struct machine *machine) struct thread *leader; pid_t pid = thread->pid_; - if (pid == thread->tid) { + if (pid == thread->tid || pid == -1) { thread->mg = map_groups__new(); } else { leader = machine__findnew_thread(machine, pid, pid); @@ -34,6 +34,7 @@ struct thread *thread__new(pid_t pid, pid_t tid) thread->pid_ = pid; thread->tid = tid; thread->ppid = -1; + thread->cpu = -1; INIT_LIST_HEAD(&thread->comm_list); comm_str = malloc(32); @@ -41,7 +42,7 @@ struct thread *thread__new(pid_t pid, pid_t tid) goto err_thread; snprintf(comm_str, 32, ":%d", tid); - comm = comm__new(comm_str, 0); + comm = comm__new(comm_str, 0, false); free(comm_str); if (!comm) goto err_thread; @@ -60,8 +61,10 @@ void thread__delete(struct thread *thread) { struct comm *comm, *tmp; - map_groups__put(thread->mg); - thread->mg = NULL; + 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); @@ -78,19 +81,33 @@ struct comm *thread__comm(const struct thread *thread) return list_first_entry(&thread->comm_list, struct comm, list); } +struct comm *thread__exec_comm(const struct thread *thread) +{ + struct comm *comm, *last = NULL; + + list_for_each_entry(comm, &thread->comm_list, list) { + if (comm->exec) + return comm; + last = comm; + } + + return last; +} + /* CHECKME: time should always be 0 if event aren't ordered */ -int thread__set_comm(struct thread *thread, const char *str, u64 timestamp) +int __thread__set_comm(struct thread *thread, const char *str, u64 timestamp, + bool exec) { struct comm *new, *curr = thread__comm(thread); int err; /* Override latest entry if it had no specific time coverage */ - if (!curr->start) { - err = comm__override(curr, str, timestamp); + if (!curr->start && !curr->exec) { + err = comm__override(curr, str, timestamp, exec); if (err) return err; } else { - new = comm__new(str, timestamp); + new = comm__new(str, timestamp, exec); if (!new) return -ENOMEM; list_add(&new->list, &thread->comm_list); @@ -127,12 +144,12 @@ 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__fixup_overlappings(thread->mg, map, stderr); map_groups__insert(thread->mg, map); } diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index 3c0c2724f82c..8c75fa774706 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h @@ -17,6 +17,7 @@ struct thread { 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 */ @@ -37,9 +38,17 @@ static inline void thread__exited(struct thread *thread) thread->dead = true; } -int thread__set_comm(struct thread *thread, const char *comm, u64 timestamp); +int __thread__set_comm(struct thread *thread, const char *comm, u64 timestamp, + bool exec); +static inline int thread__set_comm(struct thread *thread, const char *comm, + u64 timestamp) +{ + return __thread__set_comm(thread, comm, timestamp, false); +} + int thread__comm_len(struct thread *thread); struct comm *thread__comm(const struct thread *thread); +struct comm *thread__exec_comm(const struct thread *thread); const char *thread__comm_str(const struct thread *thread); void thread__insert_map(struct thread *thread, struct map *map); int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp); diff --git a/tools/perf/util/tool.h b/tools/perf/util/tool.h index 4385816d3d49..f11636966a0f 100644 --- a/tools/perf/util/tool.h +++ b/tools/perf/util/tool.h @@ -40,7 +40,7 @@ struct perf_tool { event_op2 tracing_data; event_op2 finished_round, build_id; - bool ordered_samples; + bool ordered_events; bool ordering_requires_timestamps; }; 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/trace-event-scripting.c b/tools/perf/util/trace-event-scripting.c index 57aaccc1692e..5c9bdd1591a9 100644 --- a/tools/perf/util/trace-event-scripting.c +++ b/tools/perf/util/trace-event-scripting.c @@ -30,6 +30,11 @@ struct scripting_context *scripting_context; +static int flush_script_unsupported(void) +{ + return 0; +} + static int stop_script_unsupported(void) { return 0; @@ -74,6 +79,7 @@ static int python_generate_script_unsupported(struct pevent *pevent struct scripting_ops python_scripting_unsupported_ops = { .name = "Python", .start_script = python_start_script_unsupported, + .flush_script = flush_script_unsupported, .stop_script = stop_script_unsupported, .process_event = process_event_unsupported, .generate_script = python_generate_script_unsupported, @@ -137,6 +143,7 @@ static int perl_generate_script_unsupported(struct pevent *pevent struct scripting_ops perl_scripting_unsupported_ops = { .name = "Perl", .start_script = perl_start_script_unsupported, + .flush_script = flush_script_unsupported, .stop_script = stop_script_unsupported, .process_event = process_event_unsupported, .generate_script = perl_generate_script_unsupported, diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h index 7b6d68688327..52aaa19e1eb1 100644 --- a/tools/perf/util/trace-event.h +++ b/tools/perf/util/trace-event.h @@ -64,6 +64,7 @@ struct perf_session; struct scripting_ops { const char *name; int (*start_script) (const char *script, int argc, const char **argv); + int (*flush_script) (void); int (*stop_script) (void); void (*process_event) (union perf_event *event, struct perf_sample *sample, 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/unwind-libdw.c b/tools/perf/util/unwind-libdw.c index 5ec80a575b50..7419768c38b1 100644 --- a/tools/perf/util/unwind-libdw.c +++ b/tools/perf/util/unwind-libdw.c @@ -3,6 +3,7 @@ #include <elfutils/libdwfl.h> #include <inttypes.h> #include <errno.h> +#include "debug.h" #include "unwind.h" #include "unwind-libdw.h" #include "machine.h" diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c index 25578b98f5c5..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, diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 95aefa78bb07..24e8d871b74e 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 @@ -12,6 +13,7 @@ #include <limits.h> #include <byteswap.h> #include <linux/kernel.h> +#include <unistd.h> /* * XXX We need to find a better place for these things... @@ -281,6 +283,18 @@ void get_term_dimensions(struct winsize *ws) ws->ws_col = 80; } +void set_term_quiet_input(struct termios *old) +{ + struct termios tc; + + tcgetattr(0, old); + tc = *old; + tc.c_lflag &= ~(ICANON | ECHO); + tc.c_cc[VMIN] = 0; + tc.c_cc[VTIME] = 0; + tcsetattr(0, TCSANOW, &tc); +} + static void set_tracing_events_path(const char *mountpoint) { snprintf(tracing_events_path, sizeof(tracing_events_path), "%s/%s", @@ -333,12 +347,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; } @@ -352,11 +363,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; } @@ -447,6 +456,7 @@ int filename__read_str(const char *filename, char **buf, size_t *sizep) size_t size = 0, alloc_size = 0; void *bf = NULL, *nbf; int fd, n, err = 0; + char sbuf[STRERR_BUFSIZE]; fd = open(filename, O_RDONLY); if (fd < 0) @@ -467,8 +477,8 @@ int filename__read_str(const char *filename, char **buf, size_t *sizep) n = read(fd, bf + size, alloc_size - size); if (n < 0) { if (size) { - pr_warning("read failed %d: %s\n", - errno, strerror(errno)); + pr_warning("read failed %d: %s\n", errno, + strerror_r(errno, sbuf, sizeof(sbuf))); err = 0; } else err = -errno; @@ -540,3 +550,39 @@ void mem_bswap_64(void *src, int byte_size) ++m; } } + +bool find_process(const char *name) +{ + size_t len = strlen(name); + DIR *dir; + struct dirent *d; + int ret = -1; + + dir = opendir(procfs__mountpoint()); + if (!dir) + return -1; + + /* Walk through the directory. */ + while (ret && (d = readdir(dir)) != NULL) { + char path[PATH_MAX]; + char *data; + size_t size; + + if ((d->d_type != DT_DIR) || + !strcmp(".", d->d_name) || + !strcmp("..", d->d_name)) + continue; + + scnprintf(path, sizeof(path), "%s/%s/comm", + procfs__mountpoint(), d->d_name); + + if (filename__read_str(path, &data, &size)) + continue; + + ret = strncmp(name, data, len); + free(data); + } + + closedir(dir); + return ret ? false : true; +} diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index 66864364ccb4..80bfdaa0e2a4 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -39,6 +39,8 @@ #define _ALL_SOURCE 1 #define _BSD_SOURCE 1 +/* glibc 2.20 deprecates _BSD_SOURCE in favour of _DEFAULT_SOURCE */ +#define _DEFAULT_SOURCE 1 #define HAS_BOOL #include <unistd.h> @@ -64,16 +66,18 @@ #include <regex.h> #include <utime.h> #include <sys/wait.h> -#include <sys/poll.h> +#include <poll.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <inttypes.h> +#include <linux/kernel.h> #include <linux/magic.h> #include <linux/types.h> #include <sys/ttydefaults.h> #include <api/fs/debugfs.h> #include <termios.h> #include <linux/bitops.h> +#include <termios.h> extern const char *graph_line; extern const char *graph_dotted_line; @@ -307,6 +311,7 @@ extern unsigned int page_size; extern int cacheline_size; void get_term_dimensions(struct winsize *ws); +void set_term_quiet_input(struct termios *old); struct parse_tag { char tag; @@ -317,6 +322,21 @@ unsigned long parse_tag_value(const char *str, struct parse_tag *tags); #define SRCLINE_UNKNOWN ((char *) "??:0") +static inline int path__join(char *bf, size_t size, + const char *path1, const char *path2) +{ + return scnprintf(bf, size, "%s%s%s", path1, path1[0] ? "/" : "", path2); +} + +static inline int path__join3(char *bf, size_t size, + const char *path1, const char *path2, + const char *path3) +{ + return scnprintf(bf, size, "%s%s%s%s%s", + path1, path1[0] ? "/" : "", + path2, path2[0] ? "/" : "", path3); +} + struct dso; char *get_srcline(struct dso *dso, unsigned long addr); @@ -330,4 +350,5 @@ void mem_bswap_64(void *src, int byte_size); void mem_bswap_32(void *src, int byte_size); const char *get_filename_for_perf_kvm(void); +bool find_process(const char *name); #endif /* GIT_COMPAT_UTIL_H */ 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__ */ |