perf/core improvements and fixes
Arnaldo Carvalho de Melo
maintainercover perf/core improvements and fixes
Hi Ingo,
Please consider pulling,
- Arnaldo
Test results at the end of this message, as usual.
The following changes since commit ce6c9da1113ad681bff27a3e376d2017f7f8a59c:
Merge remote-tracking branch 'tip/perf/urgent' into perf/core (2018-10-18 11:13:01 -0300)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux.git tags/perf-core-for-mingo-4.20-20181025
for you to fetch changes up to fe57120e18a1f9124ca758c89cc54f91333d1847:
perf script: Support total cycles count (2018-10-24 15:29:56 -0300)
----------------------------------------------------------------
perf/core improvements and fixes:
- Introduce 'perf trace --max-events' for stopping 'perf trace' when that many syscalls (enter+exit), tracepoints or other events such as page faults take place.
Support that as well on a per-event basis, e.g.:
perf trace -e sched:*switch/nr=2/,block:*_plug/nr=4/,block:*_unplug/nr=1/,net:*dev_queue/nr=3,max-stack=16/
Will stop when 2 context switches, 4 block plugs, 1 block unplug and 3 net_dev_queue tracepoints take place. (Arnaldo Carvalho de Melo)
- Poll for monitored tasks being alive in 'perf stat -p/-t', exiting when those tasks all terminate (Jiri Olsa)
- Encode -k clockid frequency into perf.data to enable timestamps derived metrics conversion into wall clock time on reporting stage. (Alexey Budankov)
- Improve Intel PT call graph from SQL database and GUI python scripts, including adopting the Qt MDI interface to allow for multiple subwindows for all the tables, helping in better visualizing the data in the SQL tables, also uses, when available, the Intel XED disassembler libraries to present the Intel PT data as x86 asm mnemonics. This last feature is not currently working in some cases, fix is being discussed (Adrian Hunter)
- Implement a ftrace function_graph view in 'perf script' when processing hardware trace data such as Intel PT (Andi Kleen)
- Better integration with the Intel XED disassembler, when available, in 'perf script' (Andi Kleen)
- Some 'perf trace' drop refcount fixes (Arnaldo Carvalho de Melo)
- Add Sparc support to 'perf annotate', jitdump (David Miller)
- Fix PLT symbols entry/header sizes properly on Sparc (David Miller)
- Fix generation of system call table failure with /tmp mounted with 'noexec' in arm64 (Hongxu Jia)
- Allow extended console debug output in 'perf script' (Milian Wolff)
- Flush output stream after events in 'perf script' verbose mode (Milian Wolff)
----------------------------------------------------------------
Adrian Hunter (17):
perf scripts python: call-graph-from-sql.py: Use SPDX license identifier perf scripts python: call-graph-from-sql.py: Provide better default column sizes perf scripts python: call-graph-from-sql.py: Set a minimum window size
perf scripts python: call-graph-from-sql.py: Change icon
perf scripts python: call-graph-from-sql.py: Make a "Main" function
perf scripts python: call-graph-from-sql.py: Separate the database details into a class perf scripts python: call-graph-from-sql.py: Add a class for global data perf scripts python: call-graph-from-sql.py: Remove use of setObjectName() perf scripts python: call-graph-from-sql.py: Factor out CallGraphModel from TreeModel perf scripts python: call-graph-from-sql.py: Add data helper functions
perf scripts python: call-graph-from-sql.py: Refactor TreeItem class
perf scripts python: call-graph-from-sql.py: Rename to exported-sql-viewer.py perf scripts python: exported-sql-viewer.py: Add support for multiple sub-windows perf scripts python: exported-sql-viewer.py: Add ability to find symbols in the call-graph perf scripts python: exported-sql-viewer.py: Add ability to shrink / enlarge font perf scripts python: exported-sql-viewer.py: Add ability to display all the database tables perf scripts python: exported-sql-viewer.py: Add All branches report
Alexey Budankov (1):
perf record: Encode -k clockid frequency into Perf trace
Andi Kleen (5):
perf script: Add --insn-trace for instruction decoding perf script: Make itrace script default to all calls tools script: Add --call-trace and --call-ret-trace perf script: Implement --graph-function
perf script: Support total cycles count
Arnaldo Carvalho de Melo (7):
tools lib subcmd: Introduce OPTION_ULONG
perf trace: Introduce --max-events
perf evsel: Introduce per event max_events property
perf evsel: Mark a evsel as disabled when asking the kernel do disable it perf trace: Drop addr_location refcounts
perf trace: Drop thread refcount in trace__event_handler() perf trace: Introduce per-event maximum number of events property
David Miller (3):
perf annotate: Add Sparc support
perf jitdump: Add Sparc support.
perf symbols: Set PLT entry/header sizes properly on Sparc
Hongxu Jia (1):
perf arm64: Fix generate system call table failed with /tmp mounted with noexec
Jiri Olsa (1):
perf stat: Poll for monitored tasks being alive
Milian Wolff (2):
perf script: Allow extended console debug output perf script: Flush output stream after events in verbose mode
tools/lib/subcmd/parse-options.c | 19 + tools/lib/subcmd/parse-options.h | 2 + tools/perf/Documentation/build-xed.txt | 19 + tools/perf/Documentation/intel-pt.txt | 2 +- tools/perf/Documentation/itrace.txt | 7 +- tools/perf/Documentation/perf-script.txt | 18 + tools/perf/Documentation/perf-trace.txt | 67 + tools/perf/arch/arm64/entry/syscalls/mksyscalltbl | 2 +- tools/perf/arch/sparc/Makefile | 2 + tools/perf/arch/sparc/annotate/instructions.c | 169 ++ tools/perf/builtin-record.c | 24 +- tools/perf/builtin-script.c | 166 +- tools/perf/builtin-stat.c | 24 + tools/perf/builtin-trace.c | 50 +- tools/perf/perf.h | 1 + tools/perf/scripts/python/call-graph-from-sql.py | 339 ---- tools/perf/scripts/python/export-to-postgresql.py | 2 +- tools/perf/scripts/python/export-to-sqlite.py | 2 +- tools/perf/scripts/python/exported-sql-viewer.py | 2128 +++++++++++++++++++++ tools/perf/util/annotate.c | 8 + tools/perf/util/auxtrace.c | 17 +- tools/perf/util/auxtrace.h | 5 +- tools/perf/util/cs-etm.c | 3 +- tools/perf/util/env.h | 1 + tools/perf/util/evlist.c | 2 +- tools/perf/util/evsel.c | 27 +- tools/perf/util/evsel.h | 5 + tools/perf/util/genelf.h | 6 + tools/perf/util/header.c | 23 + tools/perf/util/header.h | 1 + tools/perf/util/intel-bts.c | 3 +- tools/perf/util/intel-pt.c | 3 +- tools/perf/util/parse-events.c | 8 + tools/perf/util/parse-events.h | 1 + tools/perf/util/parse-events.l | 1 + tools/perf/util/symbol-elf.c | 12 +- tools/perf/util/symbol.h | 3 +- tools/perf/util/thread.h | 2 + 38 files changed, 2778 insertions(+), 396 deletions(-) create mode 100644 tools/perf/Documentation/build-xed.txt create mode 100644 tools/perf/arch/sparc/annotate/instructions.c delete mode 100644 tools/perf/scripts/python/call-graph-from-sql.py create mode 100755 tools/perf/scripts/python/exported-sql-viewer.py
Test results:
The first ones are container (docker) based builds of tools/perf with and without libelf support. Where clang is available, it is also used to build perf with/without libelf, and building with LIBCLANGLLVM=1 (built-in clang) with gcc and clang when clang and its devel libraries are installed.
The objtool and samples/bpf/ builds are disabled now that I'm switching from using the sources in a local volume to fetching them from a http server to build it inside the container, to make it easier to build in a container cluster. Those will come back later.
Several are cross builds, the ones with -x-ARCH and the android one, and those may not have all the features built, due to lack of multi-arch devel packages, available and being used so far on just a few, like debian:experimental-x-{arm64,mipsel}.
The 'perf test' one will perform a variety of tests exercising tools/perf/util/, tools/lib/{bpf,traceevent,etc}, as well as run perf commands with a variety of command line event specifications to then intercept the sys_perf_event syscall to check that the perf_event_attr fields are set up as expected, among a variety of other unit tests.
Then there is the 'make -C tools/perf build-test' ones, that build tools/perf/ with a variety of feature sets, exercising the build with an incomplete set of features as well as with a complete one. It is planned to have it run on each of the containers mentioned above, using some container orchestration infrastructure. Get in contact if interested in helping having this in place.
# dm 1 alpine:3.4 : Ok gcc (Alpine 5.3.0) 5.3.0 2 alpine:3.5 : Ok gcc (Alpine 6.2.1) 6.2.1 20160822 3 alpine:3.6 : Ok gcc (Alpine 6.3.0) 6.3.0 4 alpine:3.7 : Ok gcc (Alpine 6.4.0) 6.4.0 5 alpine:3.8 : Ok gcc (Alpine 6.4.0) 6.4.0 6 alpine:edge : Ok gcc (Alpine 6.4.0) 6.4.0 6 alpine:edge : Ok gcc (Alpine 6.4.0) 6.4.0 7 amazonlinux:1 : Ok gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28) 8 amazonlinux:2 : Ok gcc (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5) 9 android-ndk:r12b-arm : Ok arm-linux-androideabi-gcc (GCC) 4.9.x 20150123 (prerelease) 10 android-ndk:r15c-arm : Ok arm-linux-androideabi-gcc (GCC) 4.9.x 20150123 (prerelease) 11 centos:5 : Ok gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-55) 12 centos:6 : Ok gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-23) 13 centos:7 : Ok gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28) 14 debian:7 : Ok gcc (Debian 4.7.2-5) 4.7.2 15 debian:8 : Ok gcc (Debian 4.9.2-10+deb8u1) 4.9.2 16 debian:9 : Ok gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516 17 debian:experimental : Ok gcc (Debian 8.2.0-7) 8.2.0 18 debian:experimental-x-arm64 : Ok aarch64-linux-gnu-gcc (Debian 8.2.0-7) 8.2.0 19 debian:experimental-x-mips : Ok mips-linux-gnu-gcc (Debian 8.2.0-7) 8.2.0 20 debian:experimental-x-mips64 : Ok mips64-linux-gnuabi64-gcc (Debian 8.1.0-12) 8.1.0 21 debian:experimental-x-mipsel : Ok mipsel-linux-gnu-gcc (Debian 8.2.0-7) 8.2.0 22 fedora:20 : Ok gcc (GCC) 4.8.3 20140911 (Red Hat 4.8.3-7) 23 fedora:21 : Ok gcc (GCC) 4.9.2 20150212 (Red Hat 4.9.2-6) 24 fedora:22 : Ok gcc (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6) 25 fedora:23 : Ok gcc (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6) 26 fedora:24 : Ok gcc (GCC) 6.3.1 20161221 (Red Hat 6.3.1-1) 27 fedora:24-x-ARC-uClibc : Ok arc-linux-gcc (ARCompact ISA Linux uClibc toolchain 2017.09-rc2) 7.1.1 20170710 28 fedora:25 : Ok gcc (GCC) 6.4.1 20170727 (Red Hat 6.4.1-1) 29 fedora:26 : Ok gcc (GCC) 7.3.1 20180130 (Red Hat 7.3.1-2) 30 fedora:27 : Ok gcc (GCC) 7.3.1 20180712 (Red Hat 7.3.1-6) 31 fedora:28 : Ok gcc (GCC) 8.1.1 20180712 (Red Hat 8.1.1-5) 32 fedora:rawhide : Ok gcc (GCC) 8.2.1 20180905 (Red Hat 8.2.1-3) 33 gentoo-stage3-amd64:latest : Ok gcc (Gentoo 7.3.0-r3 p1.4) 7.3.0 34 mageia:5 : Ok gcc (GCC) 4.9.2 35 mageia:6 : Ok gcc (Mageia 5.5.0-1.mga6) 5.5.0 36 opensuse:13.2 : Ok gcc (SUSE Linux) 4.8.3 20140627 [gcc-4_8-branch revision 212064] 37 opensuse:42.1 : Ok gcc (SUSE Linux) 4.8.5 38 opensuse:42.2 : Ok gcc (SUSE Linux) 4.8.5 39 opensuse:42.3 : Ok gcc (SUSE Linux) 4.8.5 40 opensuse:tumbleweed : Ok gcc (SUSE Linux) 7.3.1 20180323 [gcc-7-branch revision 258812] 41 oraclelinux:6 : Ok gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-23.0.1) 42 oraclelinux:7 : Ok gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28.0.1) 43 ubuntu:12.04.5 : Ok gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 44 ubuntu:14.04.4 : Ok gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4 45 ubuntu:14.04.4-x-linaro-arm64 : Ok aarch64-linux-gnu-gcc (Linaro GCC 5.5-2017.10) 5.5.0 46 ubuntu:16.04 : Ok gcc (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 47 ubuntu:16.04-x-arm : Ok arm-linux-gnueabihf-gcc (Ubuntu/Linaro 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 48 ubuntu:16.04-x-arm64 : Ok aarch64-linux-gnu-gcc (Ubuntu/Linaro 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 49 ubuntu:16.04-x-powerpc : Ok powerpc-linux-gnu-gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 50 ubuntu:16.04-x-powerpc64 : Ok powerpc64-linux-gnu-gcc (Ubuntu/IBM 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 51 ubuntu:16.04-x-powerpc64el : Ok powerpc64le-linux-gnu-gcc (Ubuntu/IBM 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 52 ubuntu:16.04-x-s390 : Ok s390x-linux-gnu-gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 53 ubuntu:16.10 : Ok gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005 54 ubuntu:17.10 : Ok gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0 55 ubuntu:18.04 : Ok gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 56 ubuntu:18.04-x-arm : Ok arm-linux-gnueabihf-gcc (Ubuntu/Linaro 7.3.0-27ubuntu1~18.04) 7.3.0 57 ubuntu:18.04-x-arm64 : Ok aarch64-linux-gnu-gcc (Ubuntu/Linaro 7.3.0-27ubuntu1~18.04) 7.3.0 58 ubuntu:18.04-x-m68k : Ok m68k-linux-gnu-gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 59 ubuntu:18.04-x-powerpc : Ok powerpc-linux-gnu-gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 60 ubuntu:18.04-x-powerpc64 : Ok powerpc64-linux-gnu-gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 61 ubuntu:18.04-x-powerpc64el : Ok powerpc64le-linux-gnu-gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 62 ubuntu:18.04-x-riscv64 : Ok riscv64-linux-gnu-gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 63 ubuntu:18.04-x-s390 : Ok s390x-linux-gnu-gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 64 ubuntu:18.04-x-sh4 : Ok sh4-linux-gnu-gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 65 ubuntu:18.04-x-sparc64 : Ok sparc64-linux-gnu-gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 66 ubuntu:18.10 : Ok gcc (Ubuntu 8.2.0-7ubuntu1) 8.2.0
# uname -a
Linux jouet 4.19.0-rc4-00022-gad3273d5f1b9 #1 SMP Mon Sep 17 17:18:22 -03 2018 x86_64 x86_64 x86_64 GNU/Linux
# git log --oneline -1
fe57120e18a1 perf script: Support total cycles count
# perf version --build-options
perf version 4.19.rc8.gfe5712
dwarf: [ on ] # HAVE_DWARF_SUPPORT
dwarf_getlocations: [ on ] # HAVE_DWARF_GETLOCATIONS_SUPPORT
glibc: [ on ] # HAVE_GLIBC_SUPPORT
gtk2: [ on ] # HAVE_GTK2_SUPPORT
syscall_table: [ on ] # HAVE_SYSCALL_TABLE_SUPPORT
libbfd: [ on ] # HAVE_LIBBFD_SUPPORT
libelf: [ on ] # HAVE_LIBELF_SUPPORT
libnuma: [ on ] # HAVE_LIBNUMA_SUPPORT
numa_num_possible_cpus: [ on ] # HAVE_LIBNUMA_SUPPORT
libperl: [ on ] # HAVE_LIBPERL_SUPPORT
libpython: [ on ] # HAVE_LIBPYTHON_SUPPORT
libslang: [ on ] # HAVE_SLANG_SUPPORT
libcrypto: [ on ] # HAVE_LIBCRYPTO_SUPPORT
libunwind: [ on ] # HAVE_LIBUNWIND_SUPPORT
libdw-dwarf-unwind: [ on ] # HAVE_DWARF_SUPPORT
zlib: [ on ] # HAVE_ZLIB_SUPPORT
lzma: [ on ] # HAVE_LZMA_SUPPORT
get_cpuid: [ on ] # HAVE_AUXTRACE_SUPPORT
bpf: [ on ] # HAVE_LIBBPF_SUPPORT
# perf test
1: vmlinux symtab matches kallsyms : Ok
2: Detect openat syscall event : Ok
3: Detect openat syscall event on all cpus : Ok
4: Read samples using the mmap interface : Ok
5: Test data source output : Ok
6: Parse event definition strings : Ok
7: Simple expression parser : Ok
8: PERF_RECORD_* events & perf_sample fields : Ok
9: Parse perf pmu format : Ok
10: DSO data read : Ok
11: DSO data cache : Ok
12: DSO data reopen : Ok
13: Roundtrip evsel->name : Ok
14: Parse sched tracepoints fields : Ok
15: syscalls:sys_enter_openat event fields : Ok
16: Setup struct perf_event_attr : Ok
17: Match and link multiple hists : Ok
18: 'import perf' in python : Ok
19: Breakpoint overflow signal handler : Ok
20: Breakpoint overflow sampling : Ok
21: Breakpoint accounting : Ok
22: Watchpoint :
22.1: Read Only Watchpoint : Skip
22.2: Write Only Watchpoint : Ok
22.3: Read / Write Watchpoint : Ok
22.4: Modify Watchpoint : Ok
23: Number of exit events of a simple workload : Ok
24: Software clock events period values : Ok
25: Object code reading : Ok
26: Sample parsing : Ok
27: Use a dummy software event to keep tracking : Ok
28: Parse with no sample_id_all bit set : Ok
29: Filter hist entries : Ok
30: Lookup mmap thread : Ok
31: Share thread mg : Ok
32: Sort output of hist entries : Ok
33: Cumulate child hist entries : Ok
34: Track with sched_switch : Ok
35: Filter fds with revents mask in a fdarray : Ok
36: Add fd to a fdarray, making it autogrow : Ok
37: kmod_path__parse : Ok
38: Thread map : Ok
39: LLVM search and compile :
39.1: Basic BPF llvm compile : Ok
39.2: kbuild searching : Ok
39.3: Compile source for BPF prologue generation : Ok
39.4: Compile source for BPF relocation : Ok
40: Session topology : Ok
41: BPF filter :
41.1: Basic BPF filtering : Ok
41.2: BPF pinning : Ok
41.3: BPF prologue generation : Ok
41.4: BPF relocation checker : Ok
42: Synthesize thread map : Ok
43: Remove thread map : Ok
44: Synthesize cpu map : Ok
45: Synthesize stat config : Ok
46: Synthesize stat : Ok
47: Synthesize stat round : Ok
48: Synthesize attr update : Ok
49: Event times : Ok
50: Read backward ring buffer : Ok
51: Print cpu map : Ok
52: Probe SDT events : Ok
53: is_printable_array : Ok
54: Print bitmap : Ok
55: perf hooks : Ok
56: builtin clang support : Skip (not compiled in)
57: unit_number__scnprintf : Ok
58: mem2node : Ok
59: x86 rdpmc : Ok
60: Convert perf time to TSC : Ok
61: DWARF unwind : Ok
62: x86 instruction decoder - new instructions : Ok
63: x86 bp modify : Ok
64: Use vfs_getname probe to get syscall args filenames : Ok
65: Check open filename arg using perf trace + vfs_getname: Ok
66: probe libc's inet_pton & backtrace it with ping : Ok
67: Add vfs_getname probe to get syscall args filenames : Ok $ make -C tools/perf build-test
make: Entering directory '/home/acme/git/perf/tools/perf'
make_no_auxtrace_O: make NO_AUXTRACE=1
make_no_libbpf_O: make NO_LIBBPF=1
make_with_clangllvm_O: make LIBCLANGLLVM=1
make_no_libaudit_O: make NO_LIBAUDIT=1
make_no_backtrace_O: make NO_BACKTRACE=1
make_no_gtk2_O: make NO_GTK2=1
make_tags_O: make tags
make_install_bin_O: make install-bin
make_util_map_o_O: make util/map.o
make_static_O: make LDFLAGS=-static
make_no_libbionic_O: make NO_LIBBIONIC=1
make_debug_O: make DEBUG=1
make_install_prefix_slash_O: make install prefix=/tmp/krava/
make_util_pmu_bison_o_O: make util/pmu-bison.o
make_help_O: make help
make_clean_all_O: make clean all
make_no_libpython_O: make NO_LIBPYTHON=1
make_no_ui_O: make NO_NEWT=1 NO_SLANG=1 NO_GTK2=1
make_doc_O: make doc
make_no_newt_O: make NO_NEWT=1
make_minimal_O: make NO_LIBPERL=1 NO_LIBPYTHON=1 NO_NEWT=1 NO_GTK2=1 NO_DEMANGLE=1 NO_LIBELF=1 NO_LIBUNWIND=1 NO_BACKTRACE=1 NO_LIBNUMA=1 NO_LIBAUDIT=1 NO_LIBBIONIC=1 NO_LIBDW_DWARF_UNWIND=1 NO_AUXTRACE=1 NO_LIBBPF=1 NO_LIBCRYPTO=1 NO_SDT=1 NO_JVMTI=1
make_no_libunwind_O: make NO_LIBUNWIND=1
make_no_scripts_O: make NO_LIBPYTHON=1 NO_LIBPERL=1
make_install_O: make install
make_no_demangle_O: make NO_DEMANGLE=1
make_install_prefix_O: make install prefix=/tmp/krava
make_pure_O: make
make_no_libelf_O: make NO_LIBELF=1
make_with_babeltrace_O: make LIBBABELTRACE=1
make_no_libperl_O: make NO_LIBPERL=1
make_no_slang_O: make NO_SLANG=1
make_no_libnuma_O: make NO_LIBNUMA=1
make_no_libdw_dwarf_unwind_O: make NO_LIBDW_DWARF_UNWIND=1
make_perf_o_O: make perf.o
OK
make: Leaving directory '/home/acme/git/perf/tools/perf'
$Arnaldo Carvalho de Melo
maintainerPATCH 1/37 perf record: Encode -k clockid frequency into Perf trace
From: Alexey Budankov <[email protected]>
Store -k clockid frequency into Perf trace to enable timestamps derived metrics conversion into wall clock time on reporting stage.
Below is the example of perf report output:
tools/perf/perf record -k raw -- ../../matrix/linux/matrix.gcc
...
[ perf record: Captured and wrote 31.222 MB perf.data (818054 samples) ]
tools/perf/perf report --header
# ========
...
# event : name = cycles:ppp, , size = 112, { sample_period, sample_freq } = 4000, sample_type = IP|TID|TIME|PERIOD, disabled = 1, inherit = 1, mmap = 1, comm = 1, freq = 1, enable_on_exec = 1, task = 1, precise_ip = 3, sample_id_all = 1, exclude_guest = 1, mmap2 = 1, comm_exec = 1, use_clockid = 1, clockid = 4
...
# clockid frequency: 1000 MHz
...
# ========--- tools/perf/builtin-record.c | 24 ++++++++++++++++++++++-- tools/perf/perf.h | 1 + tools/perf/util/env.h | 1 + tools/perf/util/header.c | 23 +++++++++++++++++++++++ tools/perf/util/header.h | 1 + 5 files changed, 48 insertions(+), 2 deletions(-)
tools/perf/builtin-record.c +22 -2
@@ -592,6 +592,9 @@ static void record__init_features(struct record *rec)if (!rec->opts.full_auxtrace)perf_header__clear_feat (&session->header,HEADER_AUXTRACE ); +if (!(rec->opts.use_clockid && rec->opts.clockid_res_ns)) +perf_header__clear_feat (&session->header,HEADER_CLOCKID ); +perf_header__clear_feat (&session->header,HEADER_STAT ); }
@@ -897,6 +900,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)record__init_features (rec); +if (rec->opts.use_clockid && rec->opts.clockid_res_ns) + session->header.env.clockid_res_ns = rec->opts.clockid_res_ns; +if (forks) { err = perf_evlist__prepare_workload(rec->evlist, &opts->target, argv, data->is_pipe,
@@ -1337,6 +1343,19 @@ static const struct clockid_map clockids[] = { CLOCKID_END, }; +static int get_clockid_res (clockid_t clk_id,u64 *res_ns) +{ +struct timespec res; + + *res_ns =0 ; +if (!clock_getres (clk_id, &res)) + *res_ns = res.tv_nsec + res.tv_sec *NSEC_PER_SEC ; +else +pr_warning ("WARNING: Failed to determine specified clock resolution.\n" ); + +return 0 ; +} +static int parse_clockid (const struct option *opt,const char *str,int unset) {struct record_opts *opts = (struct record_opts *)opt->value;
@@ -1360,7 +1379,7 @@ static int parse_clockid(const struct option *opt, const char *str, int unset)/* if its a number, we're done */ if (sscanf (str,"%d" , &opts->clockid) ==1 ) -return 0 ; +return get_clockid_res (opts->clockid, &opts->clockid_res_ns);/* allow a "CLOCK_" prefix to the name */ if (!strncasecmp (str,"CLOCK_" ,6 ))
@@ -1369,7 +1388,8 @@ static int parse_clockid(const struct option *opt, const char *str, int unset)for (cm = clockids; cm->name; cm++) {if (!strcasecmp (str, cm->name)) { opts->clockid = cm->clockid; -return 0 ; +return get_clockid_res (opts->clockid, + &opts->clockid_res_ns); } }
tools/perf/perf.h +1 -0
@@ -81,6 +81,7 @@ struct record_opts {unsigned initial_delay;bool use_clockid;clockid_t clockid; +u64 clockid_res_ns;unsigned int proc_map_timeout; };
tools/perf/util/env.h +1 -0
@@ -63,6 +63,7 @@ struct perf_env {struct numa_node *numa_nodes;struct memory_node *memory_nodes;unsigned long long memory_bsize; +u64 clockid_res_ns; };extern struct perf_env perf_env;
tools/perf/util/header.c +23 -0
@@ -1034,6 +1034,13 @@ static int write_auxtrace(struct feat_fd *ff,return err; } +static int write_clockid (struct feat_fd *ff, +struct perf_evlist *evlist__maybe_unused ) +{ +return do_write (ff, &ff->ph->env.clockid_res_ns, +sizeof (ff->ph->env.clockid_res_ns)); +} +static int cpu_cache_level__sort (const void *a,const void *b) {struct cpu_cache_level *cache_a = (struct cpu_cache_level *)a;
@@ -1508,6 +1515,12 @@ static void print_cpu_topology(struct feat_fd *ff, FILE *fp)fprintf (fp,"# Core ID and Socket ID information is not available\n" ); } +static void print_clockid (struct feat_fd *ff,FILE *fp) +{ +fprintf (fp,"# clockid frequency: %" PRIu64" MHz\n" , + ff->ph->env.clockid_res_ns *1000 ); +} +static void free_event_desc (struct perf_evsel *events) {struct perf_evsel *evsel;
@@ -2531,6 +2544,15 @@ static int process_mem_topology(struct feat_fd *ff,return ret; } +static int process_clockid (struct feat_fd *ff, +void *data__maybe_unused ) +{ +if (do_read_u64 (ff, &ff->ph->env.clockid_res_ns)) +return -1 ; + +return 0 ; +} +struct feature_ops {int (*write)(struct feat_fd *ff,struct perf_evlist *evlist);void (*print)(struct feat_fd *ff,FILE *fp);
@@ -2590,6 +2612,7 @@ static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = {FEAT_OPN (CACHE , cache, true),FEAT_OPR (SAMPLE_TIME , sample_time, false),FEAT_OPR (MEM_TOPOLOGY , mem_topology, true), +FEAT_OPR (CLOCKID , clockid, false) }; struct header_print_data {
tools/perf/util/header.h +1 -0
@@ -38,6 +38,7 @@ enum {HEADER_CACHE ,HEADER_SAMPLE_TIME ,HEADER_MEM_TOPOLOGY , +HEADER_CLOCKID ,HEADER_LAST_FEATURE , HEADER_FEAT_BITS = 256, };
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 2/37 perf annotate: Add Sparc support
In reply to Arnaldo Carvalho de MeloFrom: David Miller <[email protected]>
E.g.:
$ perf annotate --stdio2
Samples: 7K of event 'cycles:ppp', 4000 Hz, Event count (approx.): 3086733887
__gettimeofday /lib32/libc-2.27.so [Percent: local period]
Percent│
│
│
│ Disassembly of section .text:
│
│ 000a6fa0 <__gettimeofday@@GLIBC_2.0>:
0.47 │ save %sp, -96, %sp
0.73 │ sethi %hi(0xe9000), %l7
│ → call __frame_state_for@@GLIBC_2.0+0x480
0.30 │ add %l7, 0x58, %l7 ! e9058 <nftw64@@GLIBC_2.3.3+0x818>
1.33 │ mov %i0, %o0
│ mov %i1, %o1
0.43 │ mov 0x74, %g1
│ ta 0x10
88.92 │ ↓ bcc 30
2.95 │ clr %g1
│ neg %o0
│ mov 1, %g1
0.31 │30: cmp %g1, 0
│ bne,pn %icc, a6fe4 <__gettimeofday@@GLIBC_2.0+0x44>
│ mov %o0, %i0
1.96 │ ← return %i7 + 8
2.62 │ nop
│ sethi %hi(0), %g1
│ neg %o0, %g2
│ add %g1, 0x160, %g1
│ ld [ %l7 + %g1 ], %g1
│ st %g2, [ %g7 + %g1 ]
│ ← return %i7 + 8
│ mov -1, %o0--- tools/perf/arch/sparc/annotate/instructions.c | 169 ++++++++++++++++++++++++++ tools/perf/util/annotate.c | 8 ++ 2 files changed, 177 insertions(+) create mode 100644 tools/perf/arch/sparc/annotate/instructions.c
tools/perf/arch/sparc/annotate/instructions.c new file +169 -0
@@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 + +static int is_branch_cond (const char *cond) +{ +if (cond[0 ] =='\0' ) +return 1 ; + +if (cond[0 ] =='a' && cond[1 ] =='\0' ) +return 1 ; + +if (cond[0 ] =='c' && + (cond[1 ] =='c' || cond[1 ] =='s' ) && + cond[2 ] =='\0' ) +return 1 ; + +if (cond[0 ] =='e' && + (cond[1 ] =='\0' || + (cond[1 ] =='q' && cond[2 ] =='\0' ))) +return 1 ; + +if (cond[0 ] =='g' && + (cond[1 ] =='\0' || + (cond[1 ] =='t' && cond[2 ] =='\0' ) || + (cond[1 ] =='e' && cond[2 ] =='\0' ) || + (cond[1 ] =='e' && cond[2 ] =='u' && cond[3 ] =='\0' ))) +return 1 ; + +if (cond[0 ] =='l' && + (cond[1 ] =='\0' || + (cond[1 ] =='t' && cond[2 ] =='\0' ) || + (cond[1 ] =='u' && cond[2 ] =='\0' ) || + (cond[1 ] =='e' && cond[2 ] =='\0' ) || + (cond[1 ] =='e' && cond[2 ] =='u' && cond[3 ] =='\0' ))) +return 1 ; + +if (cond[0 ] =='n' && + (cond[1 ] =='\0' || + (cond[1 ] =='e' && cond[2 ] =='\0' ) || + (cond[1 ] =='z' && cond[2 ] =='\0' ) || + (cond[1 ] =='e' && cond[2 ] =='g' && cond[3 ] =='\0' ))) +return 1 ; + +if (cond[0 ] =='b' && + cond[1 ] =='p' && + cond[2 ] =='o' && + cond[3 ] =='s' && + cond[4 ] =='\0' ) +return 1 ; + +if (cond[0 ] =='v' && + (cond[1 ] =='c' || cond[1 ] =='s' ) && + cond[2 ] =='\0' ) +return 1 ; + +if (cond[0 ] =='b' && + cond[1 ] =='z' && + cond[2 ] =='\0' ) +return 1 ; + +return 0 ; +} + +static int is_branch_reg_cond (const char *cond) +{ +if ((cond[0 ] =='n' || cond[0 ] =='l' ) && + cond[1 ] =='z' && + cond[2 ] =='\0' ) +return 1 ; + +if (cond[0 ] =='z' && + cond[1 ] =='\0' ) +return 1 ; + +if ((cond[0 ] =='g' || cond[0 ] =='l' ) && + cond[1 ] =='e' && + cond[2 ] =='z' && + cond[3 ] =='\0' ) +return 1 ; + +if (cond[0 ] =='g' && + cond[1 ] =='z' && + cond[2 ] =='\0' ) +return 1 ; + +return 0 ; +} + +static int is_branch_float_cond (const char *cond) +{ +if (cond[0 ] =='\0' ) +return 1 ; + +if ((cond[0 ] =='a' || cond[0 ] =='e' || + cond[0 ] =='z' || cond[0 ] =='g' || + cond[0 ] =='l' || cond[0 ] =='n' || + cond[0 ] =='o' || cond[0 ] =='u' ) && + cond[1 ] =='\0' ) +return 1 ; + +if (((cond[0 ] =='g' && cond[1 ] =='e' ) || + (cond[0 ] =='l' && (cond[1 ] =='e' || + cond[1 ] =='g' )) || + (cond[0 ] =='n' && (cond[1 ] =='e' || + cond[1 ] =='z' )) || + (cond[0 ] =='u' && (cond[1 ] =='e' || + cond[1 ] =='g' || + cond[1 ] =='l' ))) && + cond[2 ] =='\0' ) +return 1 ; + +if (cond[0 ] =='u' && + (cond[1 ] =='g' || cond[1 ] =='l' ) && + cond[2 ] =='e' && + cond[3 ] =='\0' ) +return 1 ; + +return 0 ; +} + +static struct ins_ops *sparc__associate_instruction_ops (struct arch *arch,const char *name) +{ +struct ins_ops *ops =NULL ; + +if (!strcmp (name,"call" ) || + !strcmp (name,"jmp" ) || + !strcmp (name,"jmpl" )) { + ops = &call_ops; + }else if (!strcmp (name,"ret" ) || + !strcmp (name,"retl" ) || + !strcmp (name,"return" )) { + ops = &ret_ops; + }else if (!strcmp (name,"mov" )) { + ops = &mov_ops; + }else { +if (name[0 ] =='c' && + (name[1 ] =='w' || name[1 ] =='x' )) + name +=2 ; + +if (name[0 ] =='b' ) { +const char *cond = name +1 ; + +if (cond[0 ] =='r' ) { +if (is_branch_reg_cond (cond +1 )) + ops = &jump_ops; + }else if (is_branch_cond (cond)) { + ops = &jump_ops; + } + }else if (name[0 ] =='f' && name[1 ] =='b' ) { +if (is_branch_float_cond (name +2 )) + ops = &jump_ops; + } + } + +if (ops) +arch__associate_ins_ops (arch, name, ops); + +return ops; +} + +static int sparc__annotate_init (struct arch *arch,char *cpuid__maybe_unused ) +{ +if (!arch->initialized) { + arch->initialized = true; + arch->associate_instruction_ops = sparc__associate_instruction_ops; + arch->objdump.comment_char ='#' ; + } + +return 0 ; +}
tools/perf/util/annotate.c +8 -0
@@ -139,6 +139,7 @@ static int arch__associate_ins_ops(struct arch* arch, const char *name, struct i#include "arch/x86/annotate/instructions.c" #include "arch/powerpc/annotate/instructions.c" #include "arch/s390/annotate/instructions.c" +#include "arch/sparc/annotate/instructions.c" static struct arch architectures[] = { {
@@ -170,6 +171,13 @@ static struct arch architectures[] = { .comment_char = '#', }, }, + { + .name = "sparc", + .init = sparc__annotate_init, + .objdump = { + .comment_char = '#', + }, + }, };static void ins__delete (struct ins_operands *ops)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 3/37 perf jitdump: Add Sparc support.
In reply to Arnaldo Carvalho de MeloFrom: David Miller <[email protected]>
--- tools/perf/arch/sparc/Makefile | 2 ++ tools/perf/util/genelf.h | 6 ++++++ 2 files changed, 8 insertions(+)
tools/perf/arch/sparc/Makefile +2 -0
@@ -1,3 +1,5 @@ ifndef NO_DWARF PERF_HAVE_DWARF_REGS := 1 endif + +PERF_HAVE_JITDUMP := 1
tools/perf/util/genelf.h +6 -0
@@ -29,6 +29,12 @@ int jit_add_debug_info(Elf *e, uint64_t code_addr, void *debug, int nr_debug_ent#elif defined(__powerpc__)#define GEN_ELF_ARCH EM_PPC#define GEN_ELF_CLASS ELFCLASS32 +#elif defined(__sparc__) && defined(__arch64__) +#define GEN_ELF_ARCH EM_SPARCV9 +#define GEN_ELF_CLASS ELFCLASS64 +#elif defined(__sparc__) +#define GEN_ELF_ARCH EM_SPARC +#define GEN_ELF_CLASS ELFCLASS32#else #error "unsupported architecture"#endif
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 4/37 perf symbols: Set PLT entry/header sizes properly on Sparc
In reply to Arnaldo Carvalho de MeloFrom: David Miller <[email protected]>
Using the sh_entsize for both values isn't correct. It happens to be correct on x86...
For both 32-bit and 64-bit sparc, there are four PLT entries in the PLT section.
--- tools/perf/util/symbol-elf.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
tools/perf/util/symbol-elf.c +11 -1
@@ -324,7 +324,17 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss) plt_entry_size =16 ;break ; -default :/* FIXME: s390/alpha/mips/parisc/poperpc/sh/sparc/xtensa need to be checked */ +case EM_SPARC : + plt_header_size =48 ; + plt_entry_size =12 ; +break ; + +case EM_SPARCV9 : + plt_header_size =128 ; + plt_entry_size =32 ; +break ; + +default :/* FIXME: s390/alpha/mips/parisc/poperpc/sh/xtensa need to be checked */ plt_header_size = shdr_plt.sh_entsize; plt_entry_size = shdr_plt.sh_entsize;break ;
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 5/37 perf arm64: Fix generate system call table failed with /tmp mounted with noexec
In reply to Arnaldo Carvalho de MeloFrom: Hongxu Jia <[email protected]>
When /tmp is mounted with noexec, mksyscalltbl fails.
[snip] |perf-1.0/tools/perf/arch/arm64/entry/syscalls//mksyscalltbl:
/tmp/create-table-6VGPSt: Permission denied
[snip]
Add variable TMPDIR as prefix dir of the temporary file, if it is set, replace default /tmp.
LPU-Reference: [email protected]
--- tools/perf/arch/arm64/entry/syscalls/mksyscalltbl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
tools/perf/arch/arm64/entry/syscalls/mksyscalltbl +1 -1
@@ -23,7 +23,7 @@ create_table_from_c() { local sc nr last_sc - create_table_exe=`mktemp /tmp/create-table-XXXXXX` + create_table_exe=`mktemp ${TMPDIR:-/tmp}/create-table-XXXXXX` {
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 6/37 tools lib subcmd: Introduce OPTION_ULONG
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
For completeness, will be used in 'perf trace --max-events'.
--- tools/lib/subcmd/parse-options.c | 19 +++++++++++++++++++ tools/lib/subcmd/parse-options.h | 2 ++ 2 files changed, 21 insertions(+)
tools/lib/subcmd/parse-options.c +19 -0
@@ -116,6 +116,7 @@ static int get_value(struct parse_opt_ctx_t *p,case OPTION_INTEGER :case OPTION_UINTEGER :case OPTION_LONG : +case OPTION_ULONG :case OPTION_U64 :default :break ;
@@ -166,6 +167,7 @@ static int get_value(struct parse_opt_ctx_t *p,case OPTION_INTEGER :case OPTION_UINTEGER :case OPTION_LONG : +case OPTION_ULONG :case OPTION_U64 :default :break ;
@@ -295,6 +297,22 @@ static int get_value(struct parse_opt_ctx_t *p,return opterror (opt,"expects a numerical value" , flags);return 0 ; +case OPTION_ULONG : +if (unset) { + *(unsigned long *)opt->value =0 ; +return 0 ; + } +if (opt->flags &PARSE_OPT_OPTARG && !p->opt) { + *(unsigned long *)opt->value = opt->defval; +return 0 ; + } +if (get_arg (p, opt, flags, &arg)) +return -1 ; + *(unsigned long *)opt->value =strtoul (arg, (char **)&s,10 ); +if (*s) +return opterror (opt,"expects a numerical value" , flags); +return 0 ; +case OPTION_U64 :if (unset) { *(u64 *)opt->value =0 ;
@@ -703,6 +721,7 @@ static void print_option_help(const struct option *opts, int full)case OPTION_ARGUMENT :break ;case OPTION_LONG : +case OPTION_ULONG :case OPTION_U64 :case OPTION_INTEGER :case OPTION_UINTEGER :
tools/lib/subcmd/parse-options.h +2 -0
@@ -25,6 +25,7 @@ enum parse_opt_type {OPTION_STRING ,OPTION_INTEGER ,OPTION_LONG , +OPTION_ULONG ,OPTION_CALLBACK ,OPTION_U64 , OPTION_UINTEGER,
@@ -133,6 +134,7 @@ struct option {#define OPT_INTEGER (s, l, v, h) { .type = OPTION_INTEGER, .short_name = (s), .long_name = (l), .value = check_vtype(v, int *), .help = (h) }#define OPT_UINTEGER (s, l, v, h) { .type = OPTION_UINTEGER, .short_name = (s), .long_name = (l), .value = check_vtype(v, unsigned int *), .help = (h) }#define OPT_LONG (s, l, v, h) { .type = OPTION_LONG, .short_name = (s), .long_name = (l), .value = check_vtype(v, long *), .help = (h) } +#define OPT_ULONG (s, l, v, h) { .type = OPTION_ULONG, .short_name = (s), .long_name = (l), .value = check_vtype(v, unsigned long *), .help = (h) }#define OPT_U64 (s, l, v, h) { .type = OPTION_U64, .short_name = (s), .long_name = (l), .value = check_vtype(v, u64 *), .help = (h) }#define OPT_STRING (s, l, v, a, h) { .type = OPTION_STRING, .short_name = (s), .long_name = (l), .value = check_vtype(v, const char **), .argh = (a), .help = (h) }#define OPT_STRING_OPTARG (s, l, v, a, h, d) \
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 7/37 perf trace: Introduce --max-events
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
Allow stopping tracing after a number of events take place, considering strace-like syscalls formatting as one event per enter/exit pair or when in a multi-process tracing session a syscall is interrupted and printed ending with '...'.
Examples included in the documentation:
Trace the first 4 open, openat or open_by_handle_at syscalls (in the future more syscalls may match here):
$ perf trace -e open* --max-events 4 [root@jouet perf]# trace -e open* --max-events 4 2272.992 ( 0.037 ms): gnome-shell/1370 openat(dfd: CWD, filename: /proc/self/stat) = 31 2277.481 ( 0.139 ms): gnome-shell/3039 openat(dfd: CWD, filename: /proc/self/stat) = 65 3026.398 ( 0.076 ms): gnome-shell/3039 openat(dfd: CWD, filename: /proc/self/stat) = 65 4294.665 ( 0.015 ms): sed/15879 openat(dfd: CWD, filename: /etc/ld.so.cache, flags: CLOEXEC) = 3 $
Trace the first minor page fault when running a workload:
# perf trace -F min --max-stack=7 --max-events 1 sleep 1
0.000 ( 0.000 ms): sleep/18006 minfault [__clear_user+0x1a] => 0x5626efa56080 (?k)
__clear_user ([kernel.kallsyms])
load_elf_binary ([kernel.kallsyms])
search_binary_handler ([kernel.kallsyms])
__do_execve_file.isra.33 ([kernel.kallsyms])
__x64_sys_execve ([kernel.kallsyms])
do_syscall_64 ([kernel.kallsyms])
entry_SYSCALL_64 ([kernel.kallsyms])
#Trace the next min page page fault to take place on the first CPU:
# perf trace -F min --call-graph=dwarf --max-events 1 --cpu 0
0.000 ( 0.000 ms): Web Content/17136 minfault [js::gc::Chunk::fetchNextDecommittedArena+0x4b] => 0x7fbe6181b000 (?.)
js::gc::FreeSpan::initAsEmpty (inlined)
js::gc::Arena::setAsNotAllocated (inlined)
js::gc::Chunk::fetchNextDecommittedArena (/usr/lib64/firefox/libxul.so)
js::gc::Chunk::allocateArena (/usr/lib64/firefox/libxul.so)
js::gc::GCRuntime::allocateArena (/usr/lib64/firefox/libxul.so)
js::gc::ArenaLists::allocateFromArena (/usr/lib64/firefox/libxul.so)
js::gc::GCRuntime::tryNewTenuredThing<JSString, (js::AllowGC)1> (inlined)
js::AllocateString<JSString, (js::AllowGC)1> (/usr/lib64/firefox/libxul.so)
js::Allocate<JSThinInlineString, (js::AllowGC)1> (inlined)
JSThinInlineString::new_<(js::AllowGC)1> (inlined)
AllocateInlineString<(js::AllowGC)1, unsigned char> (inlined)
js::ConcatStrings<(js::AllowGC)1> (/usr/lib64/firefox/libxul.so)
[0x18b26e6bc2bd] (/tmp/perf-17136.map)Tracing the next four ext4 operations on a specific CPU:
# perf trace -e ext4:*/call-graph=fp/ --max-events 4 --cpu 3
0.000 mutt/3849 ext4:ext4_es_lookup_extent_enter:dev 253,2 ino 57277 lblk 0
ext4_es_lookup_extent ([kernel.kallsyms])
read (/usr/lib64/libc-2.26.so)
0.097 mutt/3849 ext4:ext4_es_lookup_extent_exit:dev 253,2 ino 57277 found 0 [0/0) 0
ext4_es_lookup_extent ([kernel.kallsyms])
read (/usr/lib64/libc-2.26.so)
0.141 mutt/3849 ext4:ext4_ext_map_blocks_enter:dev 253,2 ino 57277 lblk 0 len 1 flags
ext4_ext_map_blocks ([kernel.kallsyms])
read (/usr/lib64/libc-2.26.so)
0.184 mutt/3849 ext4:ext4_ext_load_extent:dev 253,2 ino 57277 lblk 1516511 pblk 18446744071750013657
__read_extent_tree_block ([kernel.kallsyms])
__read_extent_tree_block ([kernel.kallsyms])
ext4_find_extent ([kernel.kallsyms])
ext4_ext_map_blocks ([kernel.kallsyms])
ext4_map_blocks ([kernel.kallsyms])
ext4_mpage_readpages ([kernel.kallsyms])
read_pages ([kernel.kallsyms])
__do_page_cache_readahead ([kernel.kallsyms])
ondemand_readahead ([kernel.kallsyms])
generic_file_read_iter ([kernel.kallsyms])
__vfs_read ([kernel.kallsyms])
vfs_read ([kernel.kallsyms])
ksys_read ([kernel.kallsyms])
do_syscall_64 ([kernel.kallsyms])
entry_SYSCALL_64 ([kernel.kallsyms])
read (/usr/lib64/libc-2.26.so)
#--- tools/perf/Documentation/perf-trace.txt | 47 +++++++++++++++++++++++++++++++++ tools/perf/builtin-trace.c | 21 +++++++++++++++ 2 files changed, 68 insertions(+)
tools/perf/Documentation/perf-trace.txt +47 -0
@@ -171,6 +171,11 @@ the thread executes on the designated CPUs. Default is to monitor all CPUs. --kernel-syscall-graph:: Show the kernel callchains on the syscall exit path. +--max-events=N:: + Stop after processing N events. Note that strace-like events are considered + only at exit time or when a syscall is interrupted, i.e. in those cases this + option is equivalent to the number of lines printed. + --max-stack:: Set the stack depth limit when parsing the callchain, anything beyond the specified depth will be ignored. Note that at this point
@@ -238,6 +243,48 @@ Trace syscalls, major and minor pagefaults: As you can see, there was major pagefault in python process, from CRYPTO_push_info_ routine which faulted somewhere in libcrypto.so. +Trace the first 4 open, openat or open_by_handle_at syscalls (in the future more syscalls may match here): + + $ perf trace -e open* --max-events 4 + [root@jouet perf]# trace -e open* --max-events 4 + 2272.992 ( 0.037 ms): gnome-shell/1370 openat(dfd: CWD, filename: /proc/self/stat) = 31 + 2277.481 ( 0.139 ms): gnome-shell/3039 openat(dfd: CWD, filename: /proc/self/stat) = 65 + 3026.398 ( 0.076 ms): gnome-shell/3039 openat(dfd: CWD, filename: /proc/self/stat) = 65 + 4294.665 ( 0.015 ms): sed/15879 openat(dfd: CWD, filename: /etc/ld.so.cache, flags: CLOEXEC) = 3 + $ + +Trace the first minor page fault when running a workload: + + # perf trace -F min --max-stack=7 --max-events 1 sleep 1 + 0.000 ( 0.000 ms): sleep/18006 minfault [__clear_user+0x1a] => 0x5626efa56080 (?k) + __clear_user ([kernel.kallsyms]) + load_elf_binary ([kernel.kallsyms]) + search_binary_handler ([kernel.kallsyms]) + __do_execve_file.isra.33 ([kernel.kallsyms]) + __x64_sys_execve ([kernel.kallsyms]) + do_syscall_64 ([kernel.kallsyms]) + entry_SYSCALL_64 ([kernel.kallsyms]) + # + +Trace the next min page page fault to take place on the first CPU: + + # perf trace -F min --call-graph=dwarf --max-events 1 --cpu 0 + 0.000 ( 0.000 ms): Web Content/17136 minfault [js::gc::Chunk::fetchNextDecommittedArena+0x4b] => 0x7fbe6181b000 (?.) + js::gc::FreeSpan::initAsEmpty (inlined) + js::gc::Arena::setAsNotAllocated (inlined) + js::gc::Chunk::fetchNextDecommittedArena (/usr/lib64/firefox/libxul.so) + js::gc::Chunk::allocateArena (/usr/lib64/firefox/libxul.so) + js::gc::GCRuntime::allocateArena (/usr/lib64/firefox/libxul.so) + js::gc::ArenaLists::allocateFromArena (/usr/lib64/firefox/libxul.so) + js::gc::GCRuntime::tryNewTenuredThing<JSString, (js::AllowGC)1> (inlined) + js::AllocateString<JSString, (js::AllowGC)1> (/usr/lib64/firefox/libxul.so) + js::Allocate<JSThinInlineString, (js::AllowGC)1> (inlined) + JSThinInlineString::new_<(js::AllowGC)1> (inlined) + AllocateInlineString<(js::AllowGC)1, unsigned char> (inlined) + js::ConcatStrings<(js::AllowGC)1> (/usr/lib64/firefox/libxul.so) + [0x18b26e6bc2bd] (/tmp/perf-17136.map) + # + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script[1]
tools/perf/builtin-trace.c +21 -0
@@ -89,6 +89,8 @@ struct trace {u64 base_time;FILE *output;unsigned long nr_events; +unsigned long nr_events_printed; +unsigned long max_events;struct strlist *ev_qualifier;struct {size_t nr;
@@ -1664,6 +1666,8 @@ static int trace__printf_interrupted_entry(struct trace *trace) printed +=fprintf (trace->output,"%-70s) ...\n" , ttrace->entry_str); ttrace->entry_pending = false; + ++trace->nr_events_printed; +return printed; }
@@ -1940,6 +1944,13 @@ errno_print: {fputc ('\n' , trace->output); +/* +* We only consider an 'event' for the sake of --max-events a non-filtered +* sys_enter + sys_exit and other tracepoint events. +*/ +if (++trace->nr_events_printed == trace->max_events && trace->max_events !=ULONG_MAX ) + interrupted = true; +if (callchain_ret >0 )trace__fprintf_callchain (trace, sample);else if (callchain_ret <0 )
@@ -2072,6 +2083,7 @@ static void bpf_output__fprintf(struct trace *trace, {binary__fprintf (sample->raw_data, sample->raw_size,8 , bpf_output__printer,NULL , trace->output); + ++trace->nr_events_printed; } static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel,
@@ -2127,6 +2139,7 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel,event_format__fprintf (evsel->tp_format, sample->cpu, sample->raw_data, sample->raw_size, trace->output); + ++trace->nr_events_printed; } }
@@ -2225,6 +2238,8 @@ static int trace__pgfault(struct trace *trace,trace__fprintf_callchain (trace, sample); else if (callchain_ret < 0) pr_err("Problem processing %s callchain, skipping...\n", perf_evsel__name(evsel)); + + ++trace->nr_events_printed; out: err =0 ; out_put:
@@ -2402,6 +2417,9 @@ static void trace__handle_event(struct trace *trace, union perf_event *event, sttracepoint_handler handler = evsel->handler;handler (trace, evsel, event, sample); } + +if (trace->nr_events_printed >= trace->max_events && trace->max_events !=ULONG_MAX ) + interrupted = true; }static int trace__add_syscall_newtp (struct trace *trace)
@@ -3249,6 +3267,7 @@ int cmd_trace(int argc, const char **argv) .trace_syscalls = false, .kernel_syscallchains = false, .max_stack = UINT_MAX, + .max_events = ULONG_MAX, };const char *output_name =NULL ;const struct option trace_options[] = {
@@ -3301,6 +3320,8 @@ int cmd_trace(int argc, const char **argv) &record_parse_callchain_opt),OPT_BOOLEAN (0 ,"kernel-syscall-graph" , &trace.kernel_syscallchains,"Show the kernel callchains on the syscall exit path" ), +OPT_ULONG (0 ,"max-events" , &trace.max_events, +"Set the maximum number of events to print, exit after that is reached. " ),OPT_UINTEGER (0 ,"min-stack" , &trace.min_stack,"Set the minimum stack depth when parsing the callchain, " "anything below the specified depth will be ignored."),
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 8/37 perf evsel: Introduce per event max_events property
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
This simply adds the field to 'struct perf_evsel' and allows setting it via the event parser, to test it lets trace trace:
First look at where in a function that receives an evsel we can put a probe to read how evsel->max_events was setup:
# perf probe -x ~/bin/perf -L trace__event_handler
<trace__event_handler@/home/acme/git/perf/tools/perf/builtin-trace.c:0>
0 static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel,
union perf_event *event __maybe_unused,
struct perf_sample *sample)
3 {
4 struct thread *thread = machine__findnew_thread(trace->host, sample->pid, sample->tid);
5 int callchain_ret = 0; 7 if (sample->callchain) {
8 callchain_ret = trace__resolve_callchain(trace, evsel, sample, &callchain_cursor);
9 if (callchain_ret == 0) {
10 if (callchain_cursor.nr < trace->min_stack)
11 goto out;
12 callchain_ret = 1;
}
}See what variables we can probe at line 7:
# perf probe -x ~/bin/perf -V trace__event_handler:7
Available variables at trace__event_handler:7
@<trace__event_handler+89>
int callchain_ret
struct perf_evsel* evsel
struct perf_sample* sample
struct thread* thread
struct trace* trace
union perf_event* eventAdd a probe at that line asking for evsel->max_events to be collected and named as "max_events":
# perf probe -x ~/bin/perf trace__event_handler:7 'max_events=evsel->max_events'
Added new event:
probe_perf:trace__event_handler (on trace__event_handler:7 in /home/acme/bin/perf with max_events=evsel->max_events)You can now use it in all perf tools, such as:
perf record -e probe_perf:trace__event_handler -aR sleep 1
Now use 'perf trace', here aliased to just 'trace' and trace trace, i.e. the first 'trace' is tracing just that 'probe_perf:trace__event_handler' event, while the traced trace is tracing all scheduler tracepoints, will stop at two events (--max-events 2) and will just set evsel->max_events for all the sched tracepoints to 9, we will see the output of both traces intermixed:
# trace -e *perf:*event_handler trace --max-events 2 -e sched:*/nr=9/
0.000 :0/0 sched:sched_waking:comm=rcu_sched pid=10 prio=120 target_cpu=000
0.009 :0/0 sched:sched_wakeup:comm=rcu_sched pid=10 prio=120 target_cpu=000
0.000 trace/23949 probe_perf:trace__event_handler:(48c34a) max_events=0x9
0.046 trace/23949 probe_perf:trace__event_handler:(48c34a) max_events=0x9
#Now, if the traced trace sends its output to /dev/null, we'll see just what the first level trace outputs: that evsel->max_events is indeed being set to 9:
# trace -e *perf:*event_handler trace -o /dev/null --max-events 2 -e sched:*/nr=9/
0.000 trace/23961 probe_perf:trace__event_handler:(48c34a) max_events=0x9
0.030 trace/23961 probe_perf:trace__event_handler:(48c34a) max_events=0x9
#Now that we can set evsel->max_events, we can go to the next step, honour that per-event property in 'perf trace'.
--- tools/perf/util/evsel.c | 4 ++++ tools/perf/util/evsel.h | 3 +++ tools/perf/util/parse-events.c | 8 ++++++++ tools/perf/util/parse-events.h | 1 + tools/perf/util/parse-events.l | 1 + 5 files changed, 17 insertions(+)
tools/perf/util/evsel.c +4 -0
@@ -232,6 +232,7 @@ void perf_evsel__init(struct perf_evsel *evsel, evsel->leader = evsel; evsel->unit ="" ; evsel->scale =1.0 ; + evsel->max_events =ULONG_MAX ; evsel->evlist =NULL ; evsel->bpf_fd =-1 ;INIT_LIST_HEAD (&evsel->node);
@@ -793,6 +794,9 @@ static void apply_config_terms(struct perf_evsel *evsel,case PERF_EVSEL__CONFIG_TERM_MAX_STACK : max_stack = term->val.max_stack;break ; +case PERF_EVSEL__CONFIG_TERM_MAX_EVENTS : + evsel->max_events = term->val.max_events; +break ;case PERF_EVSEL__CONFIG_TERM_INHERIT :/* * attr->inherit should has already been set by
tools/perf/util/evsel.h +3 -0
@@ -46,6 +46,7 @@ enum term_type {PERF_EVSEL__CONFIG_TERM_STACK_USER ,PERF_EVSEL__CONFIG_TERM_INHERIT ,PERF_EVSEL__CONFIG_TERM_MAX_STACK , +PERF_EVSEL__CONFIG_TERM_MAX_EVENTS ,PERF_EVSEL__CONFIG_TERM_OVERWRITE ,PERF_EVSEL__CONFIG_TERM_DRV_CFG , PERF_EVSEL__CONFIG_TERM_BRANCH,
@@ -65,6 +66,7 @@ struct perf_evsel_config_term {bool inherit;bool overwrite;char *branch; +unsigned long max_events; } val;bool weak; };
@@ -99,6 +101,7 @@ struct perf_evsel {struct perf_counts *prev_raw_counts;int idx;u32 ids; +unsigned long max_events;char *name;double scale;const char *unit;
tools/perf/util/parse-events.c +8 -0
@@ -926,6 +926,7 @@ static const char *config_term_names[__PARSE_EVENTS__TERM_TYPE_NR] = { [PARSE_EVENTS__TERM_TYPE_NOINHERIT] = "no-inherit", [PARSE_EVENTS__TERM_TYPE_INHERIT] = "inherit", [PARSE_EVENTS__TERM_TYPE_MAX_STACK] = "max-stack", + [PARSE_EVENTS__TERM_TYPE_MAX_EVENTS] = "nr", [PARSE_EVENTS__TERM_TYPE_OVERWRITE] = "overwrite", [PARSE_EVENTS__TERM_TYPE_NOOVERWRITE] = "no-overwrite", [PARSE_EVENTS__TERM_TYPE_DRV_CFG] = "driver-config",
@@ -1037,6 +1038,9 @@ do { \case PARSE_EVENTS__TERM_TYPE_MAX_STACK :CHECK_TYPE_VAL (NUM );break ; +case PARSE_EVENTS__TERM_TYPE_MAX_EVENTS : +CHECK_TYPE_VAL (NUM ); +break ;default : err->str =strdup ("unknown term" ); err->idx = term->err_term;
@@ -1084,6 +1088,7 @@ static int config_term_tracepoint(struct perf_event_attr *attr,case PARSE_EVENTS__TERM_TYPE_INHERIT :case PARSE_EVENTS__TERM_TYPE_NOINHERIT :case PARSE_EVENTS__TERM_TYPE_MAX_STACK : +case PARSE_EVENTS__TERM_TYPE_MAX_EVENTS :case PARSE_EVENTS__TERM_TYPE_OVERWRITE :case PARSE_EVENTS__TERM_TYPE_NOOVERWRITE :return config_term_common (attr, term, err);
@@ -1162,6 +1167,9 @@ do { \case PARSE_EVENTS__TERM_TYPE_MAX_STACK :ADD_CONFIG_TERM (MAX_STACK , max_stack, term->val.num);break ; +case PARSE_EVENTS__TERM_TYPE_MAX_EVENTS : +ADD_CONFIG_TERM (MAX_EVENTS , max_events, term->val.num); +break ;case PARSE_EVENTS__TERM_TYPE_OVERWRITE :ADD_CONFIG_TERM (OVERWRITE , overwrite, term->val.num ?1 :0 );break ;
tools/perf/util/parse-events.h +1 -0
@@ -71,6 +71,7 @@ enum {PARSE_EVENTS__TERM_TYPE_NOINHERIT ,PARSE_EVENTS__TERM_TYPE_INHERIT ,PARSE_EVENTS__TERM_TYPE_MAX_STACK , +PARSE_EVENTS__TERM_TYPE_MAX_EVENTS ,PARSE_EVENTS__TERM_TYPE_NOOVERWRITE ,PARSE_EVENTS__TERM_TYPE_OVERWRITE , PARSE_EVENTS__TERM_TYPE_DRV_CFG,
tools/perf/util/parse-events.l +1 -0
@@ -269,6 +269,7 @@ time { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_TIME); } call-graph { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_CALLGRAPH); } stack-size { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_STACKSIZE); } max-stack { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_MAX_STACK); } +nr { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_MAX_EVENTS); } inherit { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_INHERIT); } no-inherit { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_NOINHERIT); } overwrite { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_OVERWRITE); }
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 9/37 perf evsel: Mark a evsel as disabled when asking the kernel do disable it
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
Because there may be more such events in the ring buffer that should be discarded when an app decides to stop considering them.
At some point we'll do this with eBPF, this way we stop them at origin, before they are placed in the ring buffer.
--- tools/perf/util/evlist.c | 2 +- tools/perf/util/evsel.c | 23 +++++++++++++++++------ tools/perf/util/evsel.h | 1 + 3 files changed, 19 insertions(+), 7 deletions(-)
tools/perf/util/evlist.c +1 -1
@@ -358,7 +358,7 @@ void perf_evlist__disable(struct perf_evlist *evlist)struct perf_evsel *pos;evlist__for_each_entry (evlist, pos) { -if (!perf_evsel__is_group_leader (pos) || !pos->fd) +if (pos->disabled || !perf_evsel__is_group_leader (pos) || !pos->fd)continue ;perf_evsel__disable (pos); }
tools/perf/util/evsel.c +17 -6
@@ -1207,16 +1207,27 @@ int perf_evsel__append_addr_filter(struct perf_evsel *evsel, const char *filter)int perf_evsel__enable (struct perf_evsel *evsel) { -return perf_evsel__run_ioctl (evsel, -PERF_EVENT_IOC_ENABLE , -0 ); +int err =perf_evsel__run_ioctl (evsel,PERF_EVENT_IOC_ENABLE ,0 ); + +if (!err) + evsel->disabled = false; + +return err; }int perf_evsel__disable (struct perf_evsel *evsel) { -return perf_evsel__run_ioctl (evsel, -PERF_EVENT_IOC_DISABLE , -0 ); +int err =perf_evsel__run_ioctl (evsel,PERF_EVENT_IOC_DISABLE ,0 ); +/* +* We mark it disabled here so that tools that disable a event can +* ignore events after they disable it. I.e. the ring buffer may have +* already a few more events queued up before the kernel got the stop +* request. +*/ +if (!err) + evsel->disabled = true; + +return err; }int perf_evsel__alloc_id (struct perf_evsel *evsel,int ncpus,int nthreads)
tools/perf/util/evsel.h +1 -0
@@ -122,6 +122,7 @@ struct perf_evsel {bool snapshot;bool supported;bool needs_swap; +bool disabled;bool no_aux_samples;bool immediate;bool system_wide;
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 10/37 perf trace: Drop addr_location refcounts
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
When we use machine__resolve() we grab a reference to addr_location.thread (and in the future to other elements there) via machine__findnew_thread(), so we must pair that with addr_location__put(), else we'll never drop that thread when it exits and no other remaining data structures have pointers to it. Fix it.
--- tools/perf/builtin-trace.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)
tools/perf/builtin-trace.c +5 -3
@@ -1814,12 +1814,14 @@ static int trace__resolve_callchain(struct trace *trace, struct perf_evsel *evseint max_stack = evsel->attr.sample_max_stack ? evsel->attr.sample_max_stack : trace->max_stack; +int err; -if (machine__resolve (trace->host, &al, sample) <0 || -thread__resolve_callchain (al.thread, cursor, evsel, sample,NULL ,NULL , max_stack)) +if (machine__resolve (trace->host, &al, sample) <0 )return -1 ; -return 0 ; + err =thread__resolve_callchain (al.thread, cursor, evsel, sample,NULL ,NULL , max_stack); +addr_location__put (&al); +return err; }static int trace__fprintf_callchain (struct trace *trace,struct perf_sample *sample)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 11/37 perf trace: Drop thread refcount in trace__event_handler()
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
We must pair:
thread = machine__findnew_thread();
with thread__put(thread). Fix it.
--- tools/perf/builtin-trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
tools/perf/builtin-trace.c +1 -1
@@ -2152,8 +2152,8 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel,trace__fprintf_callchain (trace, sample); else if (callchain_ret < 0) pr_err("Problem processing %s callchain, skipping...\n", perf_evsel__name(evsel)); -thread__put (thread); out: +thread__put (thread);return 0 ; }
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 12/37 perf stat: Poll for monitored tasks being alive
In reply to Arnaldo Carvalho de MeloFrom: Jiri Olsa <[email protected]>
Adding the check for tasks we monitor via -p/-t options, and finish stat if there's no longer task to monitor.
--- tools/perf/builtin-stat.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
tools/perf/builtin-stat.c +24 -0
@@ -409,6 +409,28 @@ static struct perf_evsel *perf_evsel__reset_weak_group(struct perf_evsel *evsel)return leader; } +static bool is_target_alive (struct target *_target, +struct thread_map *threads) +{ +struct stat st; +int i; + +if (!target__has_task (_target)) +return true; + +for (i =0 ; i < threads->nr; i++) { +char path[PATH_MAX ]; + +scnprintf (path,PATH_MAX ,"%s/%d" ,procfs__mountpoint (), + threads->map[i].pid); + +if (!stat (path, &st)) +return true; + } + +return false; +} +static int __run_perf_stat (int argc,const char **argv,int run_idx) {int interval = stat_config.interval;
@@ -579,6 +601,8 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx)enable_counters ();while (!done) {nanosleep (&ts,NULL ); +if (!is_target_alive (&target, evsel_list->threads)) +break ;if (timeout)break ;if (interval) {
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 13/37 perf script: Allow extended console debug output
In reply to Arnaldo Carvalho de MeloFrom: Milian Wolff <[email protected]>
The script tool isn't using a browser, yet use_browser wasn't set explicitly to zero. This in turn lead to confusing output such as:
``` $ perf script -vvv ... ... overlapping maps in /home/milian/foobar (disable tui for more info) ... ```
Explicitly set use_browser to 0 now, which gives us the extended debug information now in perf script as expected.
--- tools/perf/builtin-script.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
tools/perf/builtin-script.c +3 -1
@@ -3417,8 +3417,10 @@ int cmd_script(int argc, const char **argv)exit (-1 ); } -if (!script_name) +if (!script_name) {setup_pager (); + use_browser =0 ; + } session =perf_session__new (&data, false, &script.tool);if (session ==NULL )
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 14/37 perf script: Flush output stream after events in verbose mode
In reply to Arnaldo Carvalho de MeloFrom: Milian Wolff <[email protected]>
When the perf script output is written to a terminal stream, the normal output of perf script would get buffered, but its debug output would be written directly. This made it quite hard to figure out where a given debug output is coming from.
We can improve on this by flushing the output buffer after processing an event. To see the value, compare the following output for a perf script -v run:
Before this patch:
```
unwind: reg 16, val 7faf7dfdc000
unwind: reg 7, val 7ffc80811e30
unwind: find_proc_info dso /usr/lib/ld-2.28.so
unwind: reg 6, val 0
unwind: _start:ip = 0x7faf7dfdc000 (0x2000)
unwind: reg 16, val 7faf7dfdc000
unwind: reg 7, val 7ffc80811e30
unwind: find_proc_info dso /usr/lib/ld-2.28.so
unwind: reg 6, val 0
unwind: _start:ip = 0x7faf7dfdc000 (0x2000)
unwind: reg 16, val 7faf7dfdc000
unwind: reg 7, val 7ffc80811e30
unwind: find_proc_info dso /usr/lib/ld-2.28.so
unwind: reg 6, val 0
unwind: _start:ip = 0x7faf7dfdc000 (0x2000)
unwind: reg 16, val 7faf7dfdc000
unwind: reg 7, val 7ffc80811e30
... lots and lots of verbose debug output
cpp-inlining 24617 90229.122036534: 1 cycles:uppp:
7faf7dfdc000 _start+0x0 (/usr/lib/ld-2.28.so)
cpp-inlining 24617 90229.122043974: 1 cycles:uppp:
7faf7dfdc000 _start+0x0 (/usr/lib/ld-2.28.so)
...
```After this patch:
```
...
unwind: reg 16, val 7faf7dfdc000
unwind: reg 7, val 7ffc80811e30
unwind: find_proc_info dso /usr/lib/ld-2.28.so
unwind: reg 6, val 0
unwind: _start:ip = 0x7faf7dfdc000 (0x2000)
cpp-inlining 24617 90229.122036534: 1 cycles:uppp:
7faf7dfdc000 _start+0x0 (/usr/lib/ld-2.28.so)
unwind: reg 16, val 7faf7dfdc000
unwind: reg 7, val 7ffc80811e30
unwind: find_proc_info dso /usr/lib/ld-2.28.so
unwind: reg 6, val 0
unwind: _start:ip = 0x7faf7dfdc000 (0x2000)
cpp-inlining 24617 90229.122043974: 1 cycles:uppp:
7faf7dfdc000 _start+0x0 (/usr/lib/ld-2.28.so)
...
```This new output format makes it much easier to use perf script output for debugging purposes, e.g. to investigate broken dwarf unwinding.
--- tools/perf/builtin-script.c | 3 +++ 1 file changed, 3 insertions(+)
tools/perf/builtin-script.c +3 -0
@@ -1737,6 +1737,9 @@ static void process_event(struct perf_script *script,if (PRINT_FIELD (METRIC ))perf_sample__fprint_metric (script, thread, evsel, sample, fp); + +if (verbose) +fflush (fp); }static struct scripting_ops *scripting_ops;
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 15/37 perf trace: Introduce per-event maximum number of events property
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
Call it 'nr', as in this context it should be expressive enough, i.e.:
# perf trace -e sched:*waking/nr=8,call-graph=fp/
0.000 :0/0 sched:sched_waking:comm=rcu_sched pid=10 prio=120 target_cpu=001
try_to_wake_up ([kernel.kallsyms])
sched_clock ([kernel.kallsyms])
3.933 :0/0 sched:sched_waking:comm=rcu_sched pid=10 prio=120 target_cpu=001
try_to_wake_up ([kernel.kallsyms])
sched_clock ([kernel.kallsyms])
3.970 IPDL Backgroun/3622 sched:sched_waking:comm=Gecko_IOThread pid=3569 prio=120 target_cpu=003
try_to_wake_up ([kernel.kallsyms])
__libc_write (/usr/lib64/libpthread-2.26.so)
20.069 IPDL Backgroun/3622 sched:sched_waking:comm=Gecko_IOThread pid=3569 prio=120 target_cpu=003
try_to_wake_up ([kernel.kallsyms])
__libc_write (/usr/lib64/libpthread-2.26.so)
37.170 IPDL Backgroun/3622 sched:sched_waking:comm=Gecko_IOThread pid=3569 prio=120 target_cpu=003
try_to_wake_up ([kernel.kallsyms])
__libc_write (/usr/lib64/libpthread-2.26.so)
53.267 IPDL Backgroun/3622 sched:sched_waking:comm=Gecko_IOThread pid=3569 prio=120 target_cpu=003
try_to_wake_up ([kernel.kallsyms])
__libc_write (/usr/lib64/libpthread-2.26.so)
70.365 IPDL Backgroun/3622 sched:sched_waking:comm=Gecko_IOThread pid=3569 prio=120 target_cpu=003
try_to_wake_up ([kernel.kallsyms])
__libc_write (/usr/lib64/libpthread-2.26.so)
75.781 Web Content/3649 sched:sched_waking:comm=JS Helper pid=3670 prio=120 target_cpu=000
try_to_wake_up ([kernel.kallsyms])
try_to_wake_up ([kernel.kallsyms])
wake_up_q ([kernel.kallsyms])
futex_wake ([kernel.kallsyms])
do_futex ([kernel.kallsyms])
__x64_sys_futex ([kernel.kallsyms])
do_syscall_64 ([kernel.kallsyms])
entry_SYSCALL_64_after_hwframe ([kernel.kallsyms])
pthread_cond_signal@@GLIBC_2.3.2 (/usr/lib64/libpthread-2.26.so)
# # perf trace -e sched:*switch/nr=2/,block:*_plug/nr=4/,block:*_unplug/nr=1/,net:*dev_queue/nr=3,max-stack=16/
0.000 :0/0 sched:sched_switch:swapper/0:0 [120] S ==> trace:3367 [120]
0.046 :0/0 sched:sched_switch:swapper/1:0 [120] S ==> kworker/u16:58:2722 [120]
570.670 irq/50-iwlwifi/680 net:net_dev_queue:dev=wlp3s0 skbaddr=0xffff93498051ef00 len=66
__dev_queue_xmit ([kernel.kallsyms])
1106.141 jbd2/dm-0-8/476 block:block_plug:[jbd2/dm-0-8]
1106.175 jbd2/dm-0-8/476 block:block_unplug:[jbd2/dm-0-8] 1
1618.088 kworker/u16:30/2694 block:block_plug:[kworker/u16:30]
1810.000 :0/0 net:net_dev_queue:dev=vnet0 skbaddr=0xffff93498051ef00 len=52
__dev_queue_xmit ([kernel.kallsyms])
3857.974 :0/0 net:net_dev_queue:dev=vnet0 skbaddr=0xffff93498051f900 len=52
__dev_queue_xmit ([kernel.kallsyms])
4790.277 jbd2/dm-2-8/748 block:block_plug:[jbd2/dm-2-8]
4790.448 jbd2/dm-2-8/748 block:block_plug:[jbd2/dm-2-8]
#The global --max-events has precendence:
# trace --max-events 3 -e sched:*switch/nr=2/,block:*_plug/nr=4/,block:*_unplug/nr=1/,net:*dev_queue/nr=3,max-stack=16/
0.000 :0/0 sched:sched_switch:swapper/0:0 [120] S ==> qemu-system-x86:2252 [120]
0.029 qemu-system-x8/2252 sched:sched_switch:qemu-system-x86:2252 [120] D ==> swapper/0:0 [120]
58.047 DNS Res~er #14/31661 net:net_dev_queue:dev=wlp3s0 skbaddr=0xffff9346966af100 len=84
__dev_queue_xmit ([kernel.kallsyms])
__libc_send (/usr/lib64/libpthread-2.26.so)
#--- tools/perf/Documentation/perf-trace.txt | 20 ++++++++++++++++++++ tools/perf/builtin-trace.c | 19 +++++++++++++++++-- tools/perf/util/evsel.h | 1 + 3 files changed, 38 insertions(+), 2 deletions(-)
tools/perf/Documentation/perf-trace.txt +20 -0
@@ -285,6 +285,26 @@ Trace the next min page page fault to take place on the first CPU: [0x18b26e6bc2bd] (/tmp/perf-17136.map) # +Trace the next two sched:sched_switch events, four block:*_plug events, the +next block:*_unplug and the next three net:*dev_queue events, this last one +with a backtrace of at most 16 entries, system wide: + + # perf trace -e sched:*switch/nr=2/,block:*_plug/nr=4/,block:*_unplug/nr=1/,net:*dev_queue/nr=3,max-stack=16/ + 0.000 :0/0 sched:sched_switch:swapper/2:0 [120] S ==> rcu_sched:10 [120] + 0.015 rcu_sched/10 sched:sched_switch:rcu_sched:10 [120] R ==> swapper/2:0 [120] + 254.198 irq/50-iwlwifi/680 net:net_dev_queue:dev=wlp3s0 skbaddr=0xffff93498051f600 len=66 + __dev_queue_xmit ([kernel.kallsyms]) + 273.977 :0/0 net:net_dev_queue:dev=wlp3s0 skbaddr=0xffff93498051f600 len=78 + __dev_queue_xmit ([kernel.kallsyms]) + 274.007 :0/0 net:net_dev_queue:dev=wlp3s0 skbaddr=0xffff93498051ff00 len=78 + __dev_queue_xmit ([kernel.kallsyms]) + 2930.140 kworker/u16:58/2722 block:block_plug:[kworker/u16:58] + 2930.162 kworker/u16:58/2722 block:block_unplug:[kworker/u16:58] 1 + 4466.094 jbd2/dm-2-8/748 block:block_plug:[jbd2/dm-2-8] + 8050.123 kworker/u16:30/2694 block:block_plug:[kworker/u16:30] + 8050.271 kworker/u16:30/2694 block:block_plug:[kworker/u16:30] + # + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script[1]
tools/perf/builtin-trace.c +17 -2
@@ -2092,8 +2092,18 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel,union perf_event *event__maybe_unused , struct perf_sample *sample) { - struct thread *thread = machine__findnew_thread(trace->host, sample->pid, sample->tid); + struct thread *thread;int callchain_ret =0 ; +/* +* Check if we called perf_evsel__disable(evsel) due to, for instance, +* this event's max_events having been hit and this is an entry coming +* from the ring buffer that we should discard, since the max events +* have already been considered/printed. +*/ +if (evsel->disabled) +return 0 ; + + thread =machine__findnew_thread (trace->host, sample->pid, sample->tid);if (sample->callchain) { callchain_ret =trace__resolve_callchain (trace, evsel, sample, &callchain_cursor);
@@ -2142,6 +2152,11 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel, sample->raw_data, sample->raw_size, trace->output); ++trace->nr_events_printed; + +if (evsel->max_events !=ULONG_MAX && ++evsel->nr_events_printed == evsel->max_events) { +perf_evsel__disable (evsel); +perf_evsel__close (evsel); + } } }
@@ -2726,7 +2741,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv)int timeout = done ?100 :-1 ;if (!draining &&perf_evlist__poll (evlist, timeout) >0 ) { -if (perf_evlist__filter_pollfd (evlist,POLLERR |POLLHUP ) ==0 ) +if (perf_evlist__filter_pollfd (evlist,POLLERR |POLLHUP |POLLNVAL ) ==0 ) draining = true; goto again;
tools/perf/util/evsel.h +1 -0
@@ -102,6 +102,7 @@ struct perf_evsel {int idx;u32 ids;unsigned long max_events; +unsigned long nr_events_printed;char *name;double scale;const char *unit;
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 16/37 perf scripts python: call-graph-from-sql.py: Use SPDX license identifier
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Use SPDX license identifier in call-graph-from-sql.py.
--- tools/perf/scripts/python/call-graph-from-sql.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-)
tools/perf/scripts/python/call-graph-from-sql.py +3 -11
@@ -1,15 +1,7 @@ #!/usr/bin/python2 -# call-graph-from-sql.py: create call-graph from sql database -# Copyright (c) 2014-2017, 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. +# SPDX-License-Identifier: GPL-2.0 +# exported-sql-viewer.py: view data from sql database +# Copyright (c) 2014-2018, Intel Corporation. # To use this script you will need to have exported data using either the # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 17/37 perf scripts python: call-graph-from-sql.py: Provide better default column sizes
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Set initial column sizes to improve initial display.
Committer testing:
Extended instructions on testing this, using the sqlite variant:
Make sure you have the SQLite glue for python+Qt installed, on fedora 27 I used:
# dnf install python-pyside
Collect some PT samples, say 5-secs worth, system wide:
# perf record -r 10 -e intel_pt//u -a sleep 5 [ perf record: Woken up 49 times to write data ] [ perf record: Captured and wrote 96.131 MB perf.data ]
This results in this perf.data file:
# ls -larth perf.data -rw-------. 1 root root 97M Oct 23 10:11 perf.data
With the following attributes:
# perf evlist -v
intel_pt//u: type: 8, size: 112, config: 0x300e601, { sample_period, sample_freq }: 1, sample_type: IP|TID|TIME|CPU|IDENTIFIER, read_format: ID, disabled: 1, inherit: 1, exclude_kernel: 1, exclude_hv: 1, sample_id_all: 1
dummy:u: type: 1, size: 112, config: 0x9, { sample_period, sample_freq }: 1, sample_type: IP|TID|TIME|CPU|IDENTIFIER, read_format: ID, inherit: 1, exclude_kernel: 1, exclude_hv: 1, mmap: 1, comm: 1, task: 1, sample_id_all: 1, mmap2: 1, comm_exec: 1, context_switch: 1
#Then generate the "pt_example" tables using:
# perf script -s ~/libexec/perf-core/scripts/python/export-to-sqlite.py pt_example branches calls 2018-10-23 10:56:59.177711 Creating database... 2018-10-23 10:56:59.195842 Writing records... instruction trace error type 1 cpu 2 pid 1644 tid 1644 ip 0x263984516750 code 5: Failed to get instruction instruction trace error type 1 cpu 2 pid 1644 tid 1644 ip 0x7f26e116fd20 code 6: Trace doesn't match instruction instruction trace error type 1 cpu 2 pid 1644 tid 1644 ip 0x7f26e162c9ee code 6: Trace doesn't match instruction instruction trace error type 1 cpu 2 pid 1644 tid 1644 ip 0x7f26e9ce831a code 6: Trace doesn't match instruction <SNIP> instruction trace error type 1 cpu 0 pid 1644 tid 1644 ip 0x7f26e13d07b4 code 6: Trace doesn't match instruction Warning: 132 instruction trace errors 2018-10-23 11:25:25.015717 Adding indexes 2018-10-23 11:25:28.788061 Done #
In my example, that perf.data file generated this db:
# file pt_example pt_example: SQLite 3.x database, last written using SQLite version 3020001 [root@seventh perf]# ls -lah pt_example -rw-r--r--. 1 root root 6.6G Oct 23 11:25 pt_example #
Then use this python script to use that db and provide a GUI:
$ python tools/perf/scripts/python/call-graph-from-sql.py pt_example branches calls
I compared the column widths before this patch and after applying it, the visual results match the patch intent.
The following patches will refer to this set of instructions in the "Committer Testing" section.
--- tools/perf/scripts/python/call-graph-from-sql.py | 3 +++ 1 file changed, 3 insertions(+)
tools/perf/scripts/python/call-graph-from-sql.py +3 -0
@@ -280,6 +280,9 @@ class MainWindow(QMainWindow): self.view = QTreeView() self.view.setModel(self.model) + for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): + self.view.setColumnWidth(c, w) + self.setCentralWidget(self.view) if __name__ == '__main__':
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 18/37 perf scripts python: call-graph-from-sql.py: Set a minimum window size
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Prevent weirdly small window size.
Committer testing:
Seems to work, but even before this patch, on my system, it always started with:
xwininfo: Window id: 0x1e00002 "Call Graph: pt_example" <SNIP>
Width: 800
Height: 600
<SNIP>
--- tools/perf/scripts/python/call-graph-from-sql.py | 1 + 1 file changed, 1 insertion(+)
tools/perf/scripts/python/call-graph-from-sql.py +1 -0
@@ -274,6 +274,7 @@ class MainWindow(QMainWindow): style = self.style() icon = style.standardIcon(QStyle.SP_MessageBoxInformation) self.setWindowIcon(icon); + self.setMinimumSize(200, 100) self.model = TreeModel(db)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 19/37 perf scripts python: call-graph-from-sql.py: Change icon
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
There are not many standard icons, but the computer icon looks slightly better than the information icon.
Committer testing:
Noticed the change on the icon on the gnome menu right next to the "Activities" menu, looks nicer indeed.
--- tools/perf/scripts/python/call-graph-from-sql.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
tools/perf/scripts/python/call-graph-from-sql.py +1 -3
@@ -271,9 +271,7 @@ class MainWindow(QMainWindow): self.setWindowTitle("Call Graph: " + dbname) self.move(100, 100) self.resize(800, 600) - style = self.style() - icon = style.standardIcon(QStyle.SP_MessageBoxInformation) - self.setWindowIcon(icon); + self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) self.setMinimumSize(200, 100) self.model = TreeModel(db)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 20/37 perf scripts python: call-graph-from-sql.py: Make a "Main" function
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Make a "Main" function so that the variables used do not pollute the global namespace.
--- tools/perf/scripts/python/call-graph-from-sql.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
tools/perf/scripts/python/call-graph-from-sql.py +6 -1
@@ -284,7 +284,9 @@ class MainWindow(QMainWindow): self.setCentralWidget(self.view) -if __name__ == '__main__': +# Main + +def Main(): if (len(sys.argv) < 2): print >> sys.stderr, "Usage is: call-graph-from-sql.py <database name>" raise Exception("Too few arguments")
@@ -331,3 +333,6 @@ if __name__ == '__main__': err = app.exec_() db.close() sys.exit(err) + +if __name__ == "__main__": + Main()
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 21/37 perf scripts python: call-graph-from-sql.py: Separate the database details into a class
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Separate the database details into a class that can provide different connections using the same connection information. That paves the way for sub-processes that require their own connection.
--- tools/perf/scripts/python/call-graph-from-sql.py | 63 ++++++++++++++---------- 1 file changed, 38 insertions(+), 25 deletions(-)
tools/perf/scripts/python/call-graph-from-sql.py +38 -25
@@ -284,6 +284,42 @@ class MainWindow(QMainWindow): self.setCentralWidget(self.view) +# Database reference + +class DBRef(): + + def __init__(self, is_sqlite3, dbname): + self.is_sqlite3 = is_sqlite3 + self.dbname = dbname + + def Open(self, connection_name): + dbname = self.dbname + if self.is_sqlite3: + db = QSqlDatabase.addDatabase("QSQLITE", connection_name) + else: + db = QSqlDatabase.addDatabase("QPSQL", connection_name) + opts = dbname.split() + for opt in opts: + if "=" in opt: + opt = opt.split("=") + if opt[0] == "hostname": + db.setHostName(opt[1]) + elif opt[0] == "port": + db.setPort(int(opt[1])) + elif opt[0] == "username": + db.setUserName(opt[1]) + elif opt[0] == "password": + db.setPassword(opt[1]) + elif opt[0] == "dbname": + dbname = opt[1] + else: + dbname = opt + + db.setDatabaseName(dbname) + if not db.open(): + raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text()) + return db, dbname + # Main def Main():
@@ -302,31 +338,8 @@ def Main(): except: pass - if is_sqlite3: - db = QSqlDatabase.addDatabase('QSQLITE') - else: - db = QSqlDatabase.addDatabase('QPSQL') - opts = dbname.split() - for opt in opts: - if '=' in opt: - opt = opt.split('=') - if opt[0] == 'hostname': - db.setHostName(opt[1]) - elif opt[0] == 'port': - db.setPort(int(opt[1])) - elif opt[0] == 'username': - db.setUserName(opt[1]) - elif opt[0] == 'password': - db.setPassword(opt[1]) - elif opt[0] == 'dbname': - dbname = opt[1] - else: - dbname = opt - - db.setDatabaseName(dbname) - if not db.open(): - raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text()) - + dbref = DBRef(is_sqlite3, dbname) + db, dbname = dbref.Open("main") app = QApplication(sys.argv) window = MainWindow(db, dbname) window.show()
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 22/37 perf scripts python: call-graph-from-sql.py: Add a class for global data
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Keep global data in a single object that is easy to pass around as needed, without polluting the global namespace.
--- tools/perf/scripts/python/call-graph-from-sql.py | 26 +++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-)
tools/perf/scripts/python/call-graph-from-sql.py +21 -5
@@ -264,17 +264,19 @@ class TreeModel(QAbstractItemModel): class MainWindow(QMainWindow): - def __init__(self, db, dbname, parent=None): + def __init__(self, glb, parent=None): super(MainWindow, self).__init__(parent) + self.glb = glb + self.setObjectName("MainWindow") - self.setWindowTitle("Call Graph: " + dbname) + self.setWindowTitle("Call Graph: " + glb.dbname) self.move(100, 100) self.resize(800, 600) self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) self.setMinimumSize(200, 100) - self.model = TreeModel(db) + self.model = TreeModel(glb.db) self.view = QTreeView() self.view.setModel(self.model)
@@ -284,6 +286,17 @@ class MainWindow(QMainWindow): self.setCentralWidget(self.view) +# Global data + +class Glb(): + + def __init__(self, dbref, db, dbname): + self.dbref = dbref + self.db = db + self.dbname = dbname + self.app = None + self.mainwindow = None + # Database reference class DBRef():
@@ -340,9 +353,12 @@ def Main(): dbref = DBRef(is_sqlite3, dbname) db, dbname = dbref.Open("main") + glb = Glb(dbref, db, dbname) app = QApplication(sys.argv) - window = MainWindow(db, dbname) - window.show() + glb.app = app + mainwindow = MainWindow(glb) + glb.mainwindow = mainwindow + mainwindow.show() err = app.exec_() db.close() sys.exit(err)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 23/37 perf scripts python: call-graph-from-sql.py: Remove use of setObjectName()
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
The object name is never used, so don't bother setting it.
--- tools/perf/scripts/python/call-graph-from-sql.py | 1 - 1 file changed, 1 deletion(-)
tools/perf/scripts/python/call-graph-from-sql.py +0 -1
@@ -269,7 +269,6 @@ class MainWindow(QMainWindow): self.glb = glb - self.setObjectName("MainWindow") self.setWindowTitle("Call Graph: " + glb.dbname) self.move(100, 100) self.resize(800, 600)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 24/37 perf scripts python: call-graph-from-sql.py: Factor out CallGraphModel from TreeModel
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Factor out CallGraphModel from TreeModel, which paves the way to reuse TreeModel in future reports.
--- tools/perf/scripts/python/call-graph-from-sql.py | 90 ++++++++++++++++-------- 1 file changed, 61 insertions(+), 29 deletions(-)
tools/perf/scripts/python/call-graph-from-sql.py +61 -29
@@ -201,42 +201,47 @@ class TreeItem(): self.selectCalls() return self.child_count - def columnCount(self): - return 7 - - def columnHeader(self, column): - headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "] - return headers[column] + def hasChildren(self): + if not self.query_done: + return True + return self.child_count > 0 def getData(self, column): return self.data[column] +# Tree data model + class TreeModel(QAbstractItemModel): - def __init__(self, db, parent=None): + def __init__(self, root, parent=None): super(TreeModel, self).__init__(parent) - self.db = db - self.root = TreeItem(db, 0, None) + self.root = root + self.last_row_read = 0 - def columnCount(self, parent): - return self.root.columnCount() - - def rowCount(self, parent): + def Item(self, parent): if parent.isValid(): - parent_item = parent.internalPointer() + return parent.internalPointer() else: - parent_item = self.root - return parent_item.childCount() + return self.root + + def rowCount(self, parent): + result = self.Item(parent).childCount() + if result < 0: + result = 0 + self.dataChanged.emit(parent, parent) + return result + + def hasChildren(self, parent): + return self.Item(parent).hasChildren() def headerData(self, section, orientation, role): if role == Qt.TextAlignmentRole: - if section > 1: - return Qt.AlignRight + return self.columnAlignment(section) if role != Qt.DisplayRole: return None if orientation != Qt.Horizontal: return None - return self.root.columnHeader(section) + return self.columnHeader(section) def parent(self, child): child_item = child.internalPointer()
@@ -246,21 +251,48 @@ class TreeModel(QAbstractItemModel): return self.createIndex(parent_item.getRow(), 0, parent_item) def index(self, row, column, parent): - if parent.isValid(): - parent_item = parent.internalPointer() - else: - parent_item = self.root - child_item = parent_item.getChildItem(row) + child_item = self.Item(parent).getChildItem(row) return self.createIndex(row, column, child_item) + def DisplayData(self, item, index): + return item.getData(index.column()) + + def columnAlignment(self, column): + return Qt.AlignLeft + + def columnFont(self, column): + return None + def data(self, index, role): if role == Qt.TextAlignmentRole: - if index.column() > 1: - return Qt.AlignRight + return self.columnAlignment(index.column()) + if role == Qt.FontRole: + return self.columnFont(index.column()) if role != Qt.DisplayRole: return None - index_item = index.internalPointer() - return index_item.getData(index.column()) + item = index.internalPointer() + return self.DisplayData(item, index) + +# Context-sensitive call graph data model + +class CallGraphModel(TreeModel): + + def __init__(self, glb, parent=None): + super(CallGraphModel, self).__init__(TreeItem(glb.db, 0, None), parent) + self.glb = glb + + def columnCount(self, parent=None): + return 7 + + def columnHeader(self, column): + headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "] + return headers[column] + + def columnAlignment(self, column): + alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] + return alignment[column] + +# Main window class MainWindow(QMainWindow):
@@ -275,7 +307,7 @@ class MainWindow(QMainWindow): self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) self.setMinimumSize(200, 100) - self.model = TreeModel(glb.db) + self.model = CallGraphModel(glb) self.view = QTreeView() self.view.setModel(self.model)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 25/37 perf scripts python: call-graph-from-sql.py: Add data helper functions
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Add helper functions for a few common cases.
--- tools/perf/scripts/python/call-graph-from-sql.py | 54 +++++++++++++----------- 1 file changed, 29 insertions(+), 25 deletions(-)
tools/perf/scripts/python/call-graph-from-sql.py +29 -25
@@ -52,6 +52,28 @@ from PySide.QtGui import * from PySide.QtSql import * from decimal import * +# Data formatting helpers + +def dsoname(name): + if name == "[kernel.kallsyms]": + return "[kernel]" + return name + +# Percent to one decimal place + +def PercentToOneDP(n, d): + if not d: + return "0.0" + x = (n * Decimal(100)) / d + return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP)) + +# Helper for queries that must not fail + +def QueryExec(query, stmt): + ret = query.exec_(stmt) + if not ret: + raise Exception("Query failed: " + query.lastError().text()) + class TreeItem(): def __init__(self, db, row, parent_item):
@@ -73,9 +95,7 @@ class TreeItem(): def setUpRoot(self): self.query_done = True query = QSqlQuery(self.db) - ret = query.exec_('SELECT id, comm FROM comms') - if not ret: - raise Exception("Query failed: " + query.lastError().text()) + QueryExec(query, 'SELECT id, comm FROM comms') while query.next(): if not query.value(0): continue
@@ -91,9 +111,7 @@ class TreeItem(): self.child_items = [] self.child_count = 0 query = QSqlQuery(self.db) - ret = query.exec_('SELECT thread_id, ( SELECT pid FROM threads WHERE id = thread_id ), ( SELECT tid FROM threads WHERE id = thread_id ) FROM comm_threads WHERE comm_id = ' + str(comm_id)) - if not ret: - raise Exception("Query failed: " + query.lastError().text()) + QueryExec(query, 'SELECT thread_id, ( SELECT pid FROM threads WHERE id = thread_id ), ( SELECT tid FROM threads WHERE id = thread_id ) FROM comm_threads WHERE comm_id = ' + str(comm_id)) while query.next(): child_item = TreeItem(self.db, self.child_count, self) self.child_items.append(child_item)
@@ -114,18 +132,6 @@ class TreeItem(): def getRow(self): return self.row - def timePercent(self, b): - if not self.time: - return "0.0" - x = (b * Decimal(100)) / self.time - return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP)) - - def branchPercent(self, b): - if not self.branch_count: - return "0.0" - x = (b * Decimal(100)) / self.branch_count - return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP)) - def addChild(self, call_path_id, name, dso, count, time, branch_count): child_item = TreeItem(self.db, self.child_count, self) child_item.comm_id = self.comm_id
@@ -134,14 +140,12 @@ class TreeItem(): child_item.branch_count = branch_count child_item.time = time child_item.data[0] = name - if dso == "[kernel.kallsyms]": - dso = "[kernel]" - child_item.data[1] = dso + child_item.data[1] = dsoname(dso) child_item.data[2] = str(count) child_item.data[3] = str(time) - child_item.data[4] = self.timePercent(time) + child_item.data[4] = PercentToOneDP(time, self.time) child_item.data[5] = str(branch_count) - child_item.data[6] = self.branchPercent(branch_count) + child_item.data[6] = PercentToOneDP(branch_count, self.branch_count) self.child_items.append(child_item) self.child_count += 1
@@ -189,12 +193,12 @@ class TreeItem(): self.branch_count = total_branch_count if self.branch_count: for child_item in self.child_items: - child_item.data[6] = self.branchPercent(child_item.branch_count) + child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count) if total_time > self.time: self.time = total_time if self.time: for child_item in self.child_items: - child_item.data[4] = self.timePercent(child_item.time) + child_item.data[4] = PercentToOneDP(child_item.time, self.time) def childCount(self): if not self.query_done:
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 26/37 perf scripts python: call-graph-from-sql.py: Refactor TreeItem class
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
class TreeItem represents items at all levels of the call-graph tree. However, not all the levels represent the same data i.e. the top-level is comms, the next level is threads, and subsequent levels are functions. Consequently it is simpler to have separate classes for different levels with commonality in a base class. Refactor TreeItem class accordingly.
--- tools/perf/scripts/python/call-graph-from-sql.py | 273 +++++++++++------------ 1 file changed, 133 insertions(+), 140 deletions(-)
tools/perf/scripts/python/call-graph-from-sql.py +133 -140
@@ -74,145 +74,6 @@ def QueryExec(query, stmt): if not ret: raise Exception("Query failed: " + query.lastError().text()) -class TreeItem(): - - def __init__(self, db, row, parent_item): - self.db = db - self.row = row - self.parent_item = parent_item - self.query_done = False; - self.child_count = 0 - self.child_items = [] - self.data = ["", "", "", "", "", "", ""] - self.comm_id = 0 - self.thread_id = 0 - self.call_path_id = 1 - self.branch_count = 0 - self.time = 0 - if not parent_item: - self.setUpRoot() - - def setUpRoot(self): - self.query_done = True - query = QSqlQuery(self.db) - QueryExec(query, 'SELECT id, comm FROM comms') - while query.next(): - if not query.value(0): - continue - child_item = TreeItem(self.db, self.child_count, self) - self.child_items.append(child_item) - self.child_count += 1 - child_item.setUpLevel1(query.value(0), query.value(1)) - - def setUpLevel1(self, comm_id, comm): - self.query_done = True; - self.comm_id = comm_id - self.data[0] = comm - self.child_items = [] - self.child_count = 0 - query = QSqlQuery(self.db) - QueryExec(query, 'SELECT thread_id, ( SELECT pid FROM threads WHERE id = thread_id ), ( SELECT tid FROM threads WHERE id = thread_id ) FROM comm_threads WHERE comm_id = ' + str(comm_id)) - while query.next(): - child_item = TreeItem(self.db, self.child_count, self) - self.child_items.append(child_item) - self.child_count += 1 - child_item.setUpLevel2(comm_id, query.value(0), query.value(1), query.value(2)) - - def setUpLevel2(self, comm_id, thread_id, pid, tid): - self.comm_id = comm_id - self.thread_id = thread_id - self.data[0] = str(pid) + ":" + str(tid) - - def getChildItem(self, row): - return self.child_items[row] - - def getParentItem(self): - return self.parent_item - - def getRow(self): - return self.row - - def addChild(self, call_path_id, name, dso, count, time, branch_count): - child_item = TreeItem(self.db, self.child_count, self) - child_item.comm_id = self.comm_id - child_item.thread_id = self.thread_id - child_item.call_path_id = call_path_id - child_item.branch_count = branch_count - child_item.time = time - child_item.data[0] = name - child_item.data[1] = dsoname(dso) - child_item.data[2] = str(count) - child_item.data[3] = str(time) - child_item.data[4] = PercentToOneDP(time, self.time) - child_item.data[5] = str(branch_count) - child_item.data[6] = PercentToOneDP(branch_count, self.branch_count) - self.child_items.append(child_item) - self.child_count += 1 - - def selectCalls(self): - self.query_done = True; - query = QSqlQuery(self.db) - ret = query.exec_('SELECT id, call_path_id, branch_count, call_time, return_time, ' - '( SELECT name FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ), ' - '( SELECT short_name FROM dsos WHERE id = ( SELECT dso_id FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ) ), ' - '( SELECT ip FROM call_paths where id = call_path_id ) ' - 'FROM calls WHERE parent_call_path_id = ' + str(self.call_path_id) + ' AND comm_id = ' + str(self.comm_id) + ' AND thread_id = ' + str(self.thread_id) + - ' ORDER BY call_path_id') - if not ret: - raise Exception("Query failed: " + query.lastError().text()) - last_call_path_id = 0 - name = "" - dso = "" - count = 0 - branch_count = 0 - total_branch_count = 0 - time = 0 - total_time = 0 - while query.next(): - if query.value(1) == last_call_path_id: - count += 1 - branch_count += query.value(2) - time += query.value(4) - query.value(3) - else: - if count: - self.addChild(last_call_path_id, name, dso, count, time, branch_count) - last_call_path_id = query.value(1) - name = query.value(5) - dso = query.value(6) - count = 1 - total_branch_count += branch_count - total_time += time - branch_count = query.value(2) - time = query.value(4) - query.value(3) - if count: - self.addChild(last_call_path_id, name, dso, count, time, branch_count) - total_branch_count += branch_count - total_time += time - # Top level does not have time or branch count, so fix that here - if total_branch_count > self.branch_count: - self.branch_count = total_branch_count - if self.branch_count: - for child_item in self.child_items: - child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count) - if total_time > self.time: - self.time = total_time - if self.time: - for child_item in self.child_items: - child_item.data[4] = PercentToOneDP(child_item.time, self.time) - - def childCount(self): - if not self.query_done: - self.selectCalls() - return self.child_count - - def hasChildren(self): - if not self.query_done: - return True - return self.child_count > 0 - - def getData(self, column): - return self.data[column] - # Tree data model class TreeModel(QAbstractItemModel):
@@ -277,12 +138,144 @@ class TreeModel(QAbstractItemModel): item = index.internalPointer() return self.DisplayData(item, index) +# Context-sensitive call graph data model item base + +class CallGraphLevelItemBase(object): + + def __init__(self, glb, row, parent_item): + self.glb = glb + self.row = row + self.parent_item = parent_item + self.query_done = False; + self.child_count = 0 + self.child_items = [] + + def getChildItem(self, row): + return self.child_items[row] + + def getParentItem(self): + return self.parent_item + + def getRow(self): + return self.row + + def childCount(self): + if not self.query_done: + self.Select() + if not self.child_count: + return -1 + return self.child_count + + def hasChildren(self): + if not self.query_done: + return True + return self.child_count > 0 + + def getData(self, column): + return self.data[column] + +# Context-sensitive call graph data model level 2+ item base + +class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase): + + def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item): + super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item) + self.comm_id = comm_id + self.thread_id = thread_id + self.call_path_id = call_path_id + self.branch_count = branch_count + self.time = time + + def Select(self): + self.query_done = True; + query = QSqlQuery(self.glb.db) + QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)" + " FROM calls" + " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" + " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" + " INNER JOIN dsos ON symbols.dso_id = dsos.id" + " WHERE parent_call_path_id = " + str(self.call_path_id) + + " AND comm_id = " + str(self.comm_id) + + " AND thread_id = " + str(self.thread_id) + + " GROUP BY call_path_id, name, short_name" + " ORDER BY call_path_id") + while query.next(): + child_item = CallGraphLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self) + self.child_items.append(child_item) + self.child_count += 1 + +# Context-sensitive call graph data model level three item + +class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase): + + def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item): + super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item) + dso = dsoname(dso) + self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ] + self.dbid = call_path_id + +# Context-sensitive call graph data model level two item + +class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase): + + def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item): + super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item) + self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""] + self.dbid = thread_id + + def Select(self): + super(CallGraphLevelTwoItem, self).Select() + for child_item in self.child_items: + self.time += child_item.time + self.branch_count += child_item.branch_count + for child_item in self.child_items: + child_item.data[4] = PercentToOneDP(child_item.time, self.time) + child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count) + +# Context-sensitive call graph data model level one item + +class CallGraphLevelOneItem(CallGraphLevelItemBase): + + def __init__(self, glb, row, comm_id, comm, parent_item): + super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item) + self.data = [comm, "", "", "", "", "", ""] + self.dbid = comm_id + + def Select(self): + self.query_done = True; + query = QSqlQuery(self.glb.db) + QueryExec(query, "SELECT thread_id, pid, tid" + " FROM comm_threads" + " INNER JOIN threads ON thread_id = threads.id" + " WHERE comm_id = " + str(self.dbid)) + while query.next(): + child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self) + self.child_items.append(child_item) + self.child_count += 1 + +# Context-sensitive call graph data model root item + +class CallGraphRootItem(CallGraphLevelItemBase): + + def __init__(self, glb): + super(CallGraphRootItem, self).__init__(glb, 0, None) + self.dbid = 0 + self.query_done = True; + query = QSqlQuery(glb.db) + QueryExec(query, "SELECT id, comm FROM comms") + while query.next(): + if not query.value(0): + continue + child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self) + self.child_items.append(child_item) + self.child_count += 1 + # Context-sensitive call graph data model class CallGraphModel(TreeModel): def __init__(self, glb, parent=None): - super(CallGraphModel, self).__init__(TreeItem(glb.db, 0, None), parent) + super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent) self.glb = glb def columnCount(self, parent=None):
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 27/37 perf scripts python: call-graph-from-sql.py: Rename to exported-sql-viewer.py
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Additional reports will be added to the script so rename to reflect the more general purpose.
---
tools/perf/Documentation/intel-pt.txt | 2 +-
tools/perf/scripts/python/export-to-postgresql.py | 2 +-
tools/perf/scripts/python/export-to-sqlite.py | 2 +-
.../python/{call-graph-from-sql.py => exported-sql-viewer.py} | 6 +++---
4 files changed, 6 insertions(+), 6 deletions(-)
rename tools/perf/scripts/python/{call-graph-from-sql.py => exported-sql-viewer.py} (98%)
mode change 100644 => 100755tools/perf/Documentation/intel-pt.txt +1 -1
@@ -106,7 +106,7 @@ in transaction, respectively. While it is possible to create scripts to analyze the data, an alternative approach is available to export the data to a sqlite or postgresql database. Refer to script export-to-sqlite.py or export-to-postgresql.py for more details, -and to script call-graph-from-sql.py for an example of using the database. +and to script exported-sql-viewer.py for an example of using the database. There is also script intel-pt-events.py which provides an example of how to unpack the raw data for power events and PTWRITE.
tools/perf/scripts/python/export-to-postgresql.py +1 -1
@@ -59,7 +59,7 @@ import datetime # pt_example=# \q # # An example of using the database is provided by the script -# call-graph-from-sql.py. Refer to that script for details. +# exported-sql-viewer.py. Refer to that script for details. # # Tables: #
tools/perf/scripts/python/export-to-sqlite.py +1 -1
@@ -40,7 +40,7 @@ import datetime # sqlite> .quit # # An example of using the database is provided by the script -# call-graph-from-sql.py. Refer to that script for details. +# exported-sql-viewer.py. Refer to that script for details. # # The database structure is practically the same as created by the script # export-to-postgresql.py. Refer to that script for details. A notable
tools/perf/scripts/python/exported-sql-viewer.py renamed from tools/perf/scripts/python/call-graph-from-sql.py +3 -3
@@ -10,12 +10,12 @@ # Following on from the example in the export scripts, a # call-graph can be displayed for the pt_example database like this: # -# python tools/perf/scripts/python/call-graph-from-sql.py pt_example +# python tools/perf/scripts/python/exported-sql-viewer.py pt_example # # Note that for PostgreSQL, this script supports connecting to remote databases # by setting hostname, port, username, password, and dbname e.g. # -# python tools/perf/scripts/python/call-graph-from-sql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example" +# python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example" # # The result is a GUI window with a tree representing a context-sensitive # call-graph. Expanding a couple of levels of the tree and adjusting column
@@ -365,7 +365,7 @@ class DBRef(): def Main(): if (len(sys.argv) < 2): - print >> sys.stderr, "Usage is: call-graph-from-sql.py <database name>" + print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>" raise Exception("Too few arguments") dbname = sys.argv[1]
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 28/37 perf scripts python: exported-sql-viewer.py: Add support for multiple sub-windows
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Use Qt MDI (multiple document interface) to support multiple sub-windows. Put the data model in a cache so that each sub-window can share the same data. This allows mutiple views of the call-graph at the same time and paves the way to add more reports.
Committer testing:
Starts with a "File Reports Windows" main menu, from the "Reports" I can get what was available up to now, the "Context-Sensitivi Call Graph" option.
--- tools/perf/scripts/python/exported-sql-viewer.py | 182 +++++++++++++++++++++-- 1 file changed, 173 insertions(+), 9 deletions(-)
tools/perf/scripts/python/exported-sql-viewer.py +173 -9
@@ -47,6 +47,8 @@ # functions that it calls import sys +import weakref +import threading from PySide.QtCore import * from PySide.QtGui import * from PySide.QtSql import *
@@ -138,6 +140,23 @@ class TreeModel(QAbstractItemModel): item = index.internalPointer() return self.DisplayData(item, index) +# Model cache + +model_cache = weakref.WeakValueDictionary() +model_cache_lock = threading.Lock() + +def LookupCreateModel(model_name, create_fn): + model_cache_lock.acquire() + try: + model = model_cache[model_name] + except: + model = None + if model is None: + model = create_fn() + model_cache[model_name] = model + model_cache_lock.release() + return model + # Context-sensitive call graph data model item base class CallGraphLevelItemBase(object):
@@ -289,6 +308,144 @@ class CallGraphModel(TreeModel): alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] return alignment[column] +# Context-sensitive call graph window + +class CallGraphWindow(QMdiSubWindow): + + def __init__(self, glb, parent=None): + super(CallGraphWindow, self).__init__(parent) + + self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x)) + + self.view = QTreeView() + self.view.setModel(self.model) + + for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): + self.view.setColumnWidth(c, w) + + self.setWidget(self.view) + + AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") + +# Action Definition + +def CreateAction(label, tip, callback, parent=None, shortcut=None): + action = QAction(label, parent) + if shortcut != None: + action.setShortcuts(shortcut) + action.setStatusTip(tip) + action.triggered.connect(callback) + return action + +# Typical application actions + +def CreateExitAction(app, parent=None): + return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit) + +# Typical MDI actions + +def CreateCloseActiveWindowAction(mdi_area): + return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area) + +def CreateCloseAllWindowsAction(mdi_area): + return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area) + +def CreateTileWindowsAction(mdi_area): + return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area) + +def CreateCascadeWindowsAction(mdi_area): + return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area) + +def CreateNextWindowAction(mdi_area): + return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild) + +def CreatePreviousWindowAction(mdi_area): + return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild) + +# Typical MDI window menu + +class WindowMenu(): + + def __init__(self, mdi_area, menu): + self.mdi_area = mdi_area + self.window_menu = menu.addMenu("&Windows") + self.close_active_window = CreateCloseActiveWindowAction(mdi_area) + self.close_all_windows = CreateCloseAllWindowsAction(mdi_area) + self.tile_windows = CreateTileWindowsAction(mdi_area) + self.cascade_windows = CreateCascadeWindowsAction(mdi_area) + self.next_window = CreateNextWindowAction(mdi_area) + self.previous_window = CreatePreviousWindowAction(mdi_area) + self.window_menu.aboutToShow.connect(self.Update) + + def Update(self): + self.window_menu.clear() + sub_window_count = len(self.mdi_area.subWindowList()) + have_sub_windows = sub_window_count != 0 + self.close_active_window.setEnabled(have_sub_windows) + self.close_all_windows.setEnabled(have_sub_windows) + self.tile_windows.setEnabled(have_sub_windows) + self.cascade_windows.setEnabled(have_sub_windows) + self.next_window.setEnabled(have_sub_windows) + self.previous_window.setEnabled(have_sub_windows) + self.window_menu.addAction(self.close_active_window) + self.window_menu.addAction(self.close_all_windows) + self.window_menu.addSeparator() + self.window_menu.addAction(self.tile_windows) + self.window_menu.addAction(self.cascade_windows) + self.window_menu.addSeparator() + self.window_menu.addAction(self.next_window) + self.window_menu.addAction(self.previous_window) + if sub_window_count == 0: + return + self.window_menu.addSeparator() + nr = 1 + for sub_window in self.mdi_area.subWindowList(): + label = str(nr) + " " + sub_window.name + if nr < 10: + label = "&" + label + action = self.window_menu.addAction(label) + action.setCheckable(True) + action.setChecked(sub_window == self.mdi_area.activeSubWindow()) + action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x)) + self.window_menu.addAction(action) + nr += 1 + + def setActiveSubWindow(self, nr): + self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) + +# Unique name for sub-windows + +def NumberedWindowName(name, nr): + if nr > 1: + name += " <" + str(nr) + ">" + return name + +def UniqueSubWindowName(mdi_area, name): + nr = 1 + while True: + unique_name = NumberedWindowName(name, nr) + ok = True + for sub_window in mdi_area.subWindowList(): + if sub_window.name == unique_name: + ok = False + break + if ok: + return unique_name + nr += 1 + +# Add a sub-window + +def AddSubWindow(mdi_area, sub_window, name): + unique_name = UniqueSubWindowName(mdi_area, name) + sub_window.setMinimumSize(200, 100) + sub_window.resize(800, 600) + sub_window.setWindowTitle(unique_name) + sub_window.setAttribute(Qt.WA_DeleteOnClose) + sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon)) + sub_window.name = unique_name + mdi_area.addSubWindow(sub_window) + sub_window.show() + # Main window class MainWindow(QMainWindow):
@@ -298,21 +455,28 @@ class MainWindow(QMainWindow): self.glb = glb - self.setWindowTitle("Call Graph: " + glb.dbname) - self.move(100, 100) - self.resize(800, 600) + self.setWindowTitle("Exported SQL Viewer: " + glb.dbname) self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) self.setMinimumSize(200, 100) - self.model = CallGraphModel(glb) + self.mdi_area = QMdiArea() + self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - self.view = QTreeView() - self.view.setModel(self.model) + self.setCentralWidget(self.mdi_area) - for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): - self.view.setColumnWidth(c, w) + menu = self.menuBar() + + file_menu = menu.addMenu("&File") + file_menu.addAction(CreateExitAction(glb.app, self)) + + reports_menu = menu.addMenu("&Reports") + reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) + + self.window_menu = WindowMenu(self.mdi_area, menu) - self.setCentralWidget(self.view) + def NewCallGraph(self): + CallGraphWindow(self.glb, self) # Global data
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 29/37 perf scripts python: exported-sql-viewer.py: Add ability to find symbols in the call-graph
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Add a Find bar that appears at the bottom of the call-graph window.
Committer testing:
Using:
python tools/perf/scripts/python/exported-sql-viewer.py pt_example branches calls
Using the database built in the first "Committer Testing" section in this patch series I was able to:
"Reports"
"Context-Sensitive Call Graphs"
Control+F or select "Edit" in the top menu then "Find"
__poll<ENTER>
and find the first place where the "__poll" function appears, then press the down arrow in the lower right corner and go to the next, etc.
--- tools/perf/scripts/python/exported-sql-viewer.py | 306 ++++++++++++++++++++++- 1 file changed, 305 insertions(+), 1 deletion(-)
tools/perf/scripts/python/exported-sql-viewer.py +305 -1
@@ -49,6 +49,7 @@ import sys import weakref import threading +import string from PySide.QtCore import * from PySide.QtGui import * from PySide.QtSql import *
@@ -76,6 +77,27 @@ def QueryExec(query, stmt): if not ret: raise Exception("Query failed: " + query.lastError().text()) +# Background thread + +class Thread(QThread): + + done = Signal(object) + + def __init__(self, task, param=None, parent=None): + super(Thread, self).__init__(parent) + self.task = task + self.param = param + + def run(self): + while True: + if self.param is None: + done, result = self.task() + else: + done, result = self.task(self.param) + self.done.emit(result) + if done: + break + # Tree data model class TreeModel(QAbstractItemModel):
@@ -157,6 +179,125 @@ def LookupCreateModel(model_name, create_fn): model_cache_lock.release() return model +# Find bar + +class FindBar(): + + def __init__(self, parent, finder, is_reg_expr=False): + self.finder = finder + self.context = [] + self.last_value = None + self.last_pattern = None + + label = QLabel("Find:") + label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + + self.textbox = QComboBox() + self.textbox.setEditable(True) + self.textbox.currentIndexChanged.connect(self.ValueChanged) + + self.progress = QProgressBar() + self.progress.setRange(0, 0) + self.progress.hide() + + if is_reg_expr: + self.pattern = QCheckBox("Regular Expression") + else: + self.pattern = QCheckBox("Pattern") + self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + + self.next_button = QToolButton() + self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown)) + self.next_button.released.connect(lambda: self.NextPrev(1)) + + self.prev_button = QToolButton() + self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp)) + self.prev_button.released.connect(lambda: self.NextPrev(-1)) + + self.close_button = QToolButton() + self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton)) + self.close_button.released.connect(self.Deactivate) + + self.hbox = QHBoxLayout() + self.hbox.setContentsMargins(0, 0, 0, 0) + + self.hbox.addWidget(label) + self.hbox.addWidget(self.textbox) + self.hbox.addWidget(self.progress) + self.hbox.addWidget(self.pattern) + self.hbox.addWidget(self.next_button) + self.hbox.addWidget(self.prev_button) + self.hbox.addWidget(self.close_button) + + self.bar = QWidget() + self.bar.setLayout(self.hbox); + self.bar.hide() + + def Widget(self): + return self.bar + + def Activate(self): + self.bar.show() + self.textbox.setFocus() + + def Deactivate(self): + self.bar.hide() + + def Busy(self): + self.textbox.setEnabled(False) + self.pattern.hide() + self.next_button.hide() + self.prev_button.hide() + self.progress.show() + + def Idle(self): + self.textbox.setEnabled(True) + self.progress.hide() + self.pattern.show() + self.next_button.show() + self.prev_button.show() + + def Find(self, direction): + value = self.textbox.currentText() + pattern = self.pattern.isChecked() + self.last_value = value + self.last_pattern = pattern + self.finder.Find(value, direction, pattern, self.context) + + def ValueChanged(self): + value = self.textbox.currentText() + pattern = self.pattern.isChecked() + index = self.textbox.currentIndex() + data = self.textbox.itemData(index) + # Store the pattern in the combo box to keep it with the text value + if data == None: + self.textbox.setItemData(index, pattern) + else: + self.pattern.setChecked(data) + self.Find(0) + + def NextPrev(self, direction): + value = self.textbox.currentText() + pattern = self.pattern.isChecked() + if value != self.last_value: + index = self.textbox.findText(value) + # Allow for a button press before the value has been added to the combo box + if index < 0: + index = self.textbox.count() + self.textbox.addItem(value, pattern) + self.textbox.setCurrentIndex(index) + return + else: + self.textbox.setItemData(index, pattern) + elif pattern != self.last_pattern: + # Keep the pattern recorded in the combo box up to date + index = self.textbox.currentIndex() + self.textbox.setItemData(index, pattern) + self.Find(direction) + + def NotFound(self): + QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found") + # Context-sensitive call graph data model item base class CallGraphLevelItemBase(object):
@@ -308,6 +449,123 @@ class CallGraphModel(TreeModel): alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] return alignment[column] + def FindSelect(self, value, pattern, query): + if pattern: + # postgresql and sqlite pattern patching differences: + # postgresql LIKE is case sensitive but sqlite LIKE is not + # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not + # postgresql supports ILIKE which is case insensitive + # sqlite supports GLOB (text only) which uses * and ? and is case sensitive + if not self.glb.dbref.is_sqlite3: + # Escape % and _ + s = value.replace("%", "\%") + s = s.replace("_", "\_") + # Translate * and ? into SQL LIKE pattern characters % and _ + trans = string.maketrans("*?", "%_") + match = " LIKE '" + str(s).translate(trans) + "'" + else: + match = " GLOB '" + str(value) + "'" + else: + match = " = '" + str(value) + "'" + QueryExec(query, "SELECT call_path_id, comm_id, thread_id" + " FROM calls" + " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" + " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" + " WHERE symbols.name" + match + + " GROUP BY comm_id, thread_id, call_path_id" + " ORDER BY comm_id, thread_id, call_path_id") + + def FindPath(self, query): + # Turn the query result into a list of ids that the tree view can walk + # to open the tree at the right place. + ids = [] + parent_id = query.value(0) + while parent_id: + ids.insert(0, parent_id) + q2 = QSqlQuery(self.glb.db) + QueryExec(q2, "SELECT parent_id" + " FROM call_paths" + " WHERE id = " + str(parent_id)) + if not q2.next(): + break + parent_id = q2.value(0) + # The call path root is not used + if ids[0] == 1: + del ids[0] + ids.insert(0, query.value(2)) + ids.insert(0, query.value(1)) + return ids + + def Found(self, query, found): + if found: + return self.FindPath(query) + return [] + + def FindValue(self, value, pattern, query, last_value, last_pattern): + if last_value == value and pattern == last_pattern: + found = query.first() + else: + self.FindSelect(value, pattern, query) + found = query.next() + return self.Found(query, found) + + def FindNext(self, query): + found = query.next() + if not found: + found = query.first() + return self.Found(query, found) + + def FindPrev(self, query): + found = query.previous() + if not found: + found = query.last() + return self.Found(query, found) + + def FindThread(self, c): + if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern: + ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern) + elif c.direction > 0: + ids = self.FindNext(c.query) + else: + ids = self.FindPrev(c.query) + return (True, ids) + + def Find(self, value, direction, pattern, context, callback): + class Context(): + def __init__(self, *x): + self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x + def Update(self, *x): + self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern) + if len(context): + context[0].Update(value, direction, pattern) + else: + context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None)) + # Use a thread so the UI is not blocked during the SELECT + thread = Thread(self.FindThread, context[0]) + thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection) + thread.start() + + def FindDone(self, thread, callback, ids): + callback(ids) + +# Vertical widget layout + +class VBox(): + + def __init__(self, w1, w2, w3=None): + self.vbox = QWidget() + self.vbox.setLayout(QVBoxLayout()); + + self.vbox.layout().setContentsMargins(0, 0, 0, 0) + + self.vbox.layout().addWidget(w1) + self.vbox.layout().addWidget(w2) + if w3: + self.vbox.layout().addWidget(w3) + + def Widget(self): + return self.vbox + # Context-sensitive call graph window class CallGraphWindow(QMdiSubWindow):
@@ -323,10 +581,45 @@ class CallGraphWindow(QMdiSubWindow): for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): self.view.setColumnWidth(c, w) - self.setWidget(self.view) + self.find_bar = FindBar(self, self) + + self.vbox = VBox(self.view, self.find_bar.Widget()) + + self.setWidget(self.vbox.Widget()) AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") + def DisplayFound(self, ids): + if not len(ids): + return False + parent = QModelIndex() + for dbid in ids: + found = False + n = self.model.rowCount(parent) + for row in xrange(n): + child = self.model.index(row, 0, parent) + if child.internalPointer().dbid == dbid: + found = True + self.view.setCurrentIndex(child) + parent = child + break + if not found: + break + return found + + def Find(self, value, direction, pattern, context): + self.view.setFocus() + self.find_bar.Busy() + self.model.Find(value, direction, pattern, context, self.FindDone) + + def FindDone(self, ids): + found = True + if not self.DisplayFound(ids): + found = False + self.find_bar.Idle() + if not found: + self.find_bar.NotFound() + # Action Definition def CreateAction(label, tip, callback, parent=None, shortcut=None):
@@ -470,11 +763,22 @@ class MainWindow(QMainWindow): file_menu = menu.addMenu("&File") file_menu.addAction(CreateExitAction(glb.app, self)) + edit_menu = menu.addMenu("&Edit") + edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find)) + reports_menu = menu.addMenu("&Reports") reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) self.window_menu = WindowMenu(self.mdi_area, menu) + def Find(self): + win = self.mdi_area.activeSubWindow() + if win: + try: + win.find_bar.Activate() + except: + pass + def NewCallGraph(self): CallGraphWindow(self.glb, self)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 30/37 perf scripts python: exported-sql-viewer.py: Add ability to shrink / enlarge font
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Shrinking the font allows more information to display.
Committer testing:
Works, tested with the convenient Control+Shift+'+' and Control+'-' as well with the more cumbersome top menu "Edit" + "Enlarge/Shrink font" options.
--- tools/perf/scripts/python/exported-sql-viewer.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
tools/perf/scripts/python/exported-sql-viewer.py +24 -0
@@ -706,6 +706,20 @@ class WindowMenu(): def setActiveSubWindow(self, nr): self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) +# Font resize + +def ResizeFont(widget, diff): + font = widget.font() + sz = font.pointSize() + font.setPointSize(sz + diff) + widget.setFont(font) + +def ShrinkFont(widget): + ResizeFont(widget, -1) + +def EnlargeFont(widget): + ResizeFont(widget, 1) + # Unique name for sub-windows def NumberedWindowName(name, nr):
@@ -765,6 +779,8 @@ class MainWindow(QMainWindow): edit_menu = menu.addMenu("&Edit") edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find)) + edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")])) + edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")])) reports_menu = menu.addMenu("&Reports") reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
@@ -779,6 +795,14 @@ class MainWindow(QMainWindow): except: pass + def ShrinkFont(self): + win = self.mdi_area.activeSubWindow() + ShrinkFont(win.view) + + def EnlargeFont(self): + win = self.mdi_area.activeSubWindow() + EnlargeFont(win.view) + def NewCallGraph(self): CallGraphWindow(self.glb, self)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 31/37 perf scripts python: exported-sql-viewer.py: Add ability to display all the database tables
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Displaying all the database tables can help make the database easier to understand.
Committer testing:
Opened all the tables, even the sqlite master table, which I selected everything and used control+C, lets see if it works...
CREATE VIEW threads_view AS SELECT id,machine_id,(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest,process_id,pid,tid FROM threads
Humm, nope, just one of the cells got copied, even with everything selected :-)
Anyway, works as advertised, useful for perusing the data.
--- tools/perf/scripts/python/exported-sql-viewer.py | 694 +++++++++++++++++++++++ 1 file changed, 694 insertions(+)
tools/perf/scripts/python/exported-sql-viewer.py +694 -0
@@ -50,10 +50,15 @@ import sys import weakref import threading import string +import cPickle +import re +import os from PySide.QtCore import * from PySide.QtGui import * from PySide.QtSql import * from decimal import * +from ctypes import * +from multiprocessing import Process, Array, Value, Event # Data formatting helpers
@@ -146,6 +151,68 @@ class TreeModel(QAbstractItemModel): def DisplayData(self, item, index): return item.getData(index.column()) + def FetchIfNeeded(self, row): + if row > self.last_row_read: + self.last_row_read = row + if row + 10 >= self.root.child_count: + self.fetcher.Fetch(glb_chunk_sz) + + def columnAlignment(self, column): + return Qt.AlignLeft + + def columnFont(self, column): + return None + + def data(self, index, role): + if role == Qt.TextAlignmentRole: + return self.columnAlignment(index.column()) + if role == Qt.FontRole: + return self.columnFont(index.column()) + if role != Qt.DisplayRole: + return None + item = index.internalPointer() + return self.DisplayData(item, index) + +# Table data model + +class TableModel(QAbstractTableModel): + + def __init__(self, parent=None): + super(TableModel, self).__init__(parent) + self.child_count = 0 + self.child_items = [] + self.last_row_read = 0 + + def Item(self, parent): + if parent.isValid(): + return parent.internalPointer() + else: + return self + + def rowCount(self, parent): + return self.child_count + + def headerData(self, section, orientation, role): + if role == Qt.TextAlignmentRole: + return self.columnAlignment(section) + if role != Qt.DisplayRole: + return None + if orientation != Qt.Horizontal: + return None + return self.columnHeader(section) + + def index(self, row, column, parent): + return self.createIndex(row, column, self.child_items[row]) + + def DisplayData(self, item, index): + return item.getData(index.column()) + + def FetchIfNeeded(self, row): + if row > self.last_row_read: + self.last_row_read = row + if row + 10 >= self.child_count: + self.fetcher.Fetch(glb_chunk_sz) + def columnAlignment(self, column): return Qt.AlignLeft
@@ -620,6 +687,601 @@ class CallGraphWindow(QMdiSubWindow): if not found: self.find_bar.NotFound() +# Child data item finder + +class ChildDataItemFinder(): + + def __init__(self, root): + self.root = root + self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5 + self.rows = [] + self.pos = 0 + + def FindSelect(self): + self.rows = [] + if self.pattern: + pattern = re.compile(self.value) + for child in self.root.child_items: + for column_data in child.data: + if re.search(pattern, str(column_data)) is not None: + self.rows.append(child.row) + break + else: + for child in self.root.child_items: + for column_data in child.data: + if self.value in str(column_data): + self.rows.append(child.row) + break + + def FindValue(self): + self.pos = 0 + if self.last_value != self.value or self.pattern != self.last_pattern: + self.FindSelect() + if not len(self.rows): + return -1 + return self.rows[self.pos] + + def FindThread(self): + if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern: + row = self.FindValue() + elif len(self.rows): + if self.direction > 0: + self.pos += 1 + if self.pos >= len(self.rows): + self.pos = 0 + else: + self.pos -= 1 + if self.pos < 0: + self.pos = len(self.rows) - 1 + row = self.rows[self.pos] + else: + row = -1 + return (True, row) + + def Find(self, value, direction, pattern, context, callback): + self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern) + # Use a thread so the UI is not blocked + thread = Thread(self.FindThread) + thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection) + thread.start() + + def FindDone(self, thread, callback, row): + callback(row) + +# Number of database records to fetch in one go + +glb_chunk_sz = 10000 + +# size of pickled integer big enough for record size + +glb_nsz = 8 + +# Background process for SQL data fetcher + +class SQLFetcherProcess(): + + def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep): + # Need a unique connection name + conn_name = "SQLFetcher" + str(os.getpid()) + self.db, dbname = dbref.Open(conn_name) + self.sql = sql + self.buffer = buffer + self.head = head + self.tail = tail + self.fetch_count = fetch_count + self.fetching_done = fetching_done + self.process_target = process_target + self.wait_event = wait_event + self.fetched_event = fetched_event + self.prep = prep + self.query = QSqlQuery(self.db) + self.query_limit = 0 if "$$last_id$$" in sql else 2 + self.last_id = -1 + self.fetched = 0 + self.more = True + self.local_head = self.head.value + self.local_tail = self.tail.value + + def Select(self): + if self.query_limit: + if self.query_limit == 1: + return + self.query_limit -= 1 + stmt = self.sql.replace("$$last_id$$", str(self.last_id)) + QueryExec(self.query, stmt) + + def Next(self): + if not self.query.next(): + self.Select() + if not self.query.next(): + return None + self.last_id = self.query.value(0) + return self.prep(self.query) + + def WaitForTarget(self): + while True: + self.wait_event.clear() + target = self.process_target.value + if target > self.fetched or target < 0: + break + self.wait_event.wait() + return target + + def HasSpace(self, sz): + if self.local_tail <= self.local_head: + space = len(self.buffer) - self.local_head + if space > sz: + return True + if space >= glb_nsz: + # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer + nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL) + self.buffer[self.local_head : self.local_head + len(nd)] = nd + self.local_head = 0 + if self.local_tail - self.local_head > sz: + return True + return False + + def WaitForSpace(self, sz): + if self.HasSpace(sz): + return + while True: + self.wait_event.clear() + self.local_tail = self.tail.value + if self.HasSpace(sz): + return + self.wait_event.wait() + + def AddToBuffer(self, obj): + d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL) + n = len(d) + nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL) + sz = n + glb_nsz + self.WaitForSpace(sz) + pos = self.local_head + self.buffer[pos : pos + len(nd)] = nd + self.buffer[pos + glb_nsz : pos + sz] = d + self.local_head += sz + + def FetchBatch(self, batch_size): + fetched = 0 + while batch_size > fetched: + obj = self.Next() + if obj is None: + self.more = False + break + self.AddToBuffer(obj) + fetched += 1 + if fetched: + self.fetched += fetched + with self.fetch_count.get_lock(): + self.fetch_count.value += fetched + self.head.value = self.local_head + self.fetched_event.set() + + def Run(self): + while self.more: + target = self.WaitForTarget() + if target < 0: + break + batch_size = min(glb_chunk_sz, target - self.fetched) + self.FetchBatch(batch_size) + self.fetching_done.value = True + self.fetched_event.set() + +def SQLFetcherFn(*x): + process = SQLFetcherProcess(*x) + process.Run() + +# SQL data fetcher + +class SQLFetcher(QObject): + + done = Signal(object) + + def __init__(self, glb, sql, prep, process_data, parent=None): + super(SQLFetcher, self).__init__(parent) + self.process_data = process_data + self.more = True + self.target = 0 + self.last_target = 0 + self.fetched = 0 + self.buffer_size = 16 * 1024 * 1024 + self.buffer = Array(c_char, self.buffer_size, lock=False) + self.head = Value(c_longlong) + self.tail = Value(c_longlong) + self.local_tail = 0 + self.fetch_count = Value(c_longlong) + self.fetching_done = Value(c_bool) + self.last_count = 0 + self.process_target = Value(c_longlong) + self.wait_event = Event() + self.fetched_event = Event() + glb.AddInstanceToShutdownOnExit(self) + self.process = Process(target=SQLFetcherFn, args=(glb.dbref, sql, self.buffer, self.head, self.tail, self.fetch_count, self.fetching_done, self.process_target, self.wait_event, self.fetched_event, prep)) + self.process.start() + self.thread = Thread(self.Thread) + self.thread.done.connect(self.ProcessData, Qt.QueuedConnection) + self.thread.start() + + def Shutdown(self): + # Tell the thread and process to exit + self.process_target.value = -1 + self.wait_event.set() + self.more = False + self.fetching_done.value = True + self.fetched_event.set() + + def Thread(self): + if not self.more: + return True, 0 + while True: + self.fetched_event.clear() + fetch_count = self.fetch_count.value + if fetch_count != self.last_count: + break + if self.fetching_done.value: + self.more = False + return True, 0 + self.fetched_event.wait() + count = fetch_count - self.last_count + self.last_count = fetch_count + self.fetched += count + return False, count + + def Fetch(self, nr): + if not self.more: + # -1 inidcates there are no more + return -1 + result = self.fetched + extra = result + nr - self.target + if extra > 0: + self.target += extra + # process_target < 0 indicates shutting down + if self.process_target.value >= 0: + self.process_target.value = self.target + self.wait_event.set() + return result + + def RemoveFromBuffer(self): + pos = self.local_tail + if len(self.buffer) - pos < glb_nsz: + pos = 0 + n = cPickle.loads(self.buffer[pos : pos + glb_nsz]) + if n == 0: + pos = 0 + n = cPickle.loads(self.buffer[0 : glb_nsz]) + pos += glb_nsz + obj = cPickle.loads(self.buffer[pos : pos + n]) + self.local_tail = pos + n + return obj + + def ProcessData(self, count): + for i in xrange(count): + obj = self.RemoveFromBuffer() + self.process_data(obj) + self.tail.value = self.local_tail + self.wait_event.set() + self.done.emit(count) + +# Fetch more records bar + +class FetchMoreRecordsBar(): + + def __init__(self, model, parent): + self.model = model + + self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:") + self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + + self.fetch_count = QSpinBox() + self.fetch_count.setRange(1, 1000000) + self.fetch_count.setValue(10) + self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + + self.fetch = QPushButton("Go!") + self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.fetch.released.connect(self.FetchMoreRecords) + + self.progress = QProgressBar() + self.progress.setRange(0, 100) + self.progress.hide() + + self.done_label = QLabel("All records fetched") + self.done_label.hide() + + self.spacer = QLabel("") + + self.close_button = QToolButton() + self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton)) + self.close_button.released.connect(self.Deactivate) + + self.hbox = QHBoxLayout() + self.hbox.setContentsMargins(0, 0, 0, 0) + + self.hbox.addWidget(self.label) + self.hbox.addWidget(self.fetch_count) + self.hbox.addWidget(self.fetch) + self.hbox.addWidget(self.spacer) + self.hbox.addWidget(self.progress) + self.hbox.addWidget(self.done_label) + self.hbox.addWidget(self.close_button) + + self.bar = QWidget() + self.bar.setLayout(self.hbox); + self.bar.show() + + self.in_progress = False + self.model.progress.connect(self.Progress) + + self.done = False + + if not model.HasMoreRecords(): + self.Done() + + def Widget(self): + return self.bar + + def Activate(self): + self.bar.show() + self.fetch.setFocus() + + def Deactivate(self): + self.bar.hide() + + def Enable(self, enable): + self.fetch.setEnabled(enable) + self.fetch_count.setEnabled(enable) + + def Busy(self): + self.Enable(False) + self.fetch.hide() + self.spacer.hide() + self.progress.show() + + def Idle(self): + self.in_progress = False + self.Enable(True) + self.progress.hide() + self.fetch.show() + self.spacer.show() + + def Target(self): + return self.fetch_count.value() * glb_chunk_sz + + def Done(self): + self.done = True + self.Idle() + self.label.hide() + self.fetch_count.hide() + self.fetch.hide() + self.spacer.hide() + self.done_label.show() + + def Progress(self, count): + if self.in_progress: + if count: + percent = ((count - self.start) * 100) / self.Target() + if percent >= 100: + self.Idle() + else: + self.progress.setValue(percent) + if not count: + # Count value of zero means no more records + self.Done() + + def FetchMoreRecords(self): + if self.done: + return + self.progress.setValue(0) + self.Busy() + self.in_progress = True + self.start = self.model.FetchMoreRecords(self.Target()) + +# SQL data preparation + +def SQLTableDataPrep(query, count): + data = [] + for i in xrange(count): + data.append(query.value(i)) + return data + +# SQL table data model item + +class SQLTableItem(): + + def __init__(self, row, data): + self.row = row + self.data = data + + def getData(self, column): + return self.data[column] + +# SQL table data model + +class SQLTableModel(TableModel): + + progress = Signal(object) + + def __init__(self, glb, sql, column_count, parent=None): + super(SQLTableModel, self).__init__(parent) + self.glb = glb + self.more = True + self.populated = 0 + self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample) + self.fetcher.done.connect(self.Update) + self.fetcher.Fetch(glb_chunk_sz) + + def DisplayData(self, item, index): + self.FetchIfNeeded(item.row) + return item.getData(index.column()) + + def AddSample(self, data): + child = SQLTableItem(self.populated, data) + self.child_items.append(child) + self.populated += 1 + + def Update(self, fetched): + if not fetched: + self.more = False + self.progress.emit(0) + child_count = self.child_count + count = self.populated - child_count + if count > 0: + parent = QModelIndex() + self.beginInsertRows(parent, child_count, child_count + count - 1) + self.insertRows(child_count, count, parent) + self.child_count += count + self.endInsertRows() + self.progress.emit(self.child_count) + + def FetchMoreRecords(self, count): + current = self.child_count + if self.more: + self.fetcher.Fetch(count) + else: + self.progress.emit(0) + return current + + def HasMoreRecords(self): + return self.more + +# SQL automatic table data model + +class SQLAutoTableModel(SQLTableModel): + + def __init__(self, glb, table_name, parent=None): + sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz) + if table_name == "comm_threads_view": + # For now, comm_threads_view has no id column + sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz) + self.column_headers = [] + query = QSqlQuery(glb.db) + if glb.dbref.is_sqlite3: + QueryExec(query, "PRAGMA table_info(" + table_name + ")") + while query.next(): + self.column_headers.append(query.value(1)) + if table_name == "sqlite_master": + sql = "SELECT * FROM " + table_name + else: + if table_name[:19] == "information_schema.": + sql = "SELECT * FROM " + table_name + select_table_name = table_name[19:] + schema = "information_schema" + else: + select_table_name = table_name + schema = "public" + QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'") + while query.next(): + self.column_headers.append(query.value(0)) + super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent) + + def columnCount(self, parent=None): + return len(self.column_headers) + + def columnHeader(self, column): + return self.column_headers[column] + +# Base class for custom ResizeColumnsToContents + +class ResizeColumnsToContentsBase(QObject): + + def __init__(self, parent=None): + super(ResizeColumnsToContentsBase, self).__init__(parent) + + def ResizeColumnToContents(self, column, n): + # Using the view's resizeColumnToContents() here is extrememly slow + # so implement a crude alternative + font = self.view.font() + metrics = QFontMetrics(font) + max = 0 + for row in xrange(n): + val = self.data_model.child_items[row].data[column] + len = metrics.width(str(val) + "MM") + max = len if len > max else max + val = self.data_model.columnHeader(column) + len = metrics.width(str(val) + "MM") + max = len if len > max else max + self.view.setColumnWidth(column, max) + + def ResizeColumnsToContents(self): + n = min(self.data_model.child_count, 100) + if n < 1: + # No data yet, so connect a signal to notify when there is + self.data_model.rowsInserted.connect(self.UpdateColumnWidths) + return + columns = self.data_model.columnCount() + for i in xrange(columns): + self.ResizeColumnToContents(i, n) + + def UpdateColumnWidths(self, *x): + # This only needs to be done once, so disconnect the signal now + self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths) + self.ResizeColumnsToContents() + +# Table window + +class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase): + + def __init__(self, glb, table_name, parent=None): + super(TableWindow, self).__init__(parent) + + self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name)) + + self.model = QSortFilterProxyModel() + self.model.setSourceModel(self.data_model) + + self.view = QTableView() + self.view.setModel(self.model) + self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.view.verticalHeader().setVisible(False) + self.view.sortByColumn(-1, Qt.AscendingOrder) + self.view.setSortingEnabled(True) + + self.ResizeColumnsToContents() + + self.find_bar = FindBar(self, self, True) + + self.finder = ChildDataItemFinder(self.data_model) + + self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) + + self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) + + self.setWidget(self.vbox.Widget()) + + AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table") + + def Find(self, value, direction, pattern, context): + self.view.setFocus() + self.find_bar.Busy() + self.finder.Find(value, direction, pattern, context, self.FindDone) + + def FindDone(self, row): + self.find_bar.Idle() + if row >= 0: + self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) + else: + self.find_bar.NotFound() + +# Table list + +def GetTableList(glb): + tables = [] + query = QSqlQuery(glb.db) + if glb.dbref.is_sqlite3: + QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name") + else: + QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name") + while query.next(): + tables.append(query.value(0)) + if glb.dbref.is_sqlite3: + tables.append("sqlite_master") + else: + tables.append("information_schema.tables") + tables.append("information_schema.views") + tables.append("information_schema.columns") + return tables + # Action Definition def CreateAction(label, tip, callback, parent=None, shortcut=None):
@@ -779,12 +1441,15 @@ class MainWindow(QMainWindow): edit_menu = menu.addMenu("&Edit") edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find)) + edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)])) edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")])) edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")])) reports_menu = menu.addMenu("&Reports") reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) + self.TableMenu(GetTableList(glb), menu) + self.window_menu = WindowMenu(self.mdi_area, menu) def Find(self):
@@ -795,6 +1460,14 @@ class MainWindow(QMainWindow): except: pass + def FetchMoreRecords(self): + win = self.mdi_area.activeSubWindow() + if win: + try: + win.fetch_bar.Activate() + except: + pass + def ShrinkFont(self): win = self.mdi_area.activeSubWindow() ShrinkFont(win.view)
@@ -803,9 +1476,17 @@ class MainWindow(QMainWindow): win = self.mdi_area.activeSubWindow() EnlargeFont(win.view) + def TableMenu(self, tables, menu): + table_menu = menu.addMenu("&Tables") + for table in tables: + table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self)) + def NewCallGraph(self): CallGraphWindow(self.glb, self) + def NewTableView(self, table_name): + TableWindow(self.glb, table_name, self) + # Global data class Glb():
@@ -816,6 +1497,18 @@ class Glb(): self.dbname = dbname self.app = None self.mainwindow = None + self.instances_to_shutdown_on_exit = weakref.WeakSet() + + def AddInstanceToShutdownOnExit(self, instance): + self.instances_to_shutdown_on_exit.add(instance) + + # Shutdown any background processes or threads + def ShutdownInstances(self): + for x in self.instances_to_shutdown_on_exit: + try: + x.Shutdown() + except: + pass # Database reference
@@ -880,6 +1573,7 @@ def Main(): glb.mainwindow = mainwindow mainwindow.show() err = app.exec_() + glb.ShutdownInstances() db.close() sys.exit(err)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 32/37 perf scripts python: exported-sql-viewer.py: Add All branches report
In reply to Arnaldo Carvalho de MeloFrom: Adrian Hunter <[email protected]>
Add a report to display branches in a similar fashion to perf script. The main purpose of this report is to display disassembly, however, presently, the only supported disassembler is Intel XED, and additionally the object code must be present in perf build ID cache.
To use Intel XED, libxed.so must be present. To build and install libxed.so:
git clone https://github.com/intelxed/mbuild.git mbuild git clone https://github.com/intelxed/xed cd xed ./mfile.py --share sudo ./mfile.py --prefix=/usr/local install sudo ldconfig
--- tools/perf/scripts/python/exported-sql-viewer.py | 547 +++++++++++++++++++++++ 1 file changed, 547 insertions(+)
tools/perf/scripts/python/exported-sql-viewer.py +547 -0
@@ -46,6 +46,48 @@ # 'Branch Count' is the total number of branches for that function and all # functions that it calls +# There is also a "All branches" report, which displays branches and +# possibly disassembly. However, presently, the only supported disassembler is +# Intel XED, and additionally the object code must be present in perf build ID +# cache. To use Intel XED, libxed.so must be present. To build and install +# libxed.so: +# git clone https://github.com/intelxed/mbuild.git mbuild +# git clone https://github.com/intelxed/xed +# cd xed +# ./mfile.py --share +# sudo ./mfile.py --prefix=/usr/local install +# sudo ldconfig +# +# Example report: +# +# Time CPU Command PID TID Branch Type In Tx Branch +# 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so) +# 7fab593ea260 48 89 e7 mov %rsp, %rdi +# 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel]) +# 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so) +# 7fab593ea260 48 89 e7 mov %rsp, %rdi +# 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930 +# 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so) +# 7fab593ea930 55 pushq %rbp +# 7fab593ea931 48 89 e5 mov %rsp, %rbp +# 7fab593ea934 41 57 pushq %r15 +# 7fab593ea936 41 56 pushq %r14 +# 7fab593ea938 41 55 pushq %r13 +# 7fab593ea93a 41 54 pushq %r12 +# 7fab593ea93c 53 pushq %rbx +# 7fab593ea93d 48 89 fb mov %rdi, %rbx +# 7fab593ea940 48 83 ec 68 sub $0x68, %rsp +# 7fab593ea944 0f 31 rdtsc +# 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx +# 7fab593ea94a 89 c0 mov %eax, %eax +# 7fab593ea94c 48 09 c2 or %rax, %rdx +# 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax +# 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel]) +# 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so) +# 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax +# 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip) +# 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel]) + import sys import weakref import threading
@@ -62,6 +104,16 @@ from multiprocessing import Process, Array, Value, Event # Data formatting helpers +def tohex(ip): + if ip < 0: + ip += 1 << 64 + return "%x" % ip + +def offstr(offset): + if offset: + return "+0x%x" % offset + return "" + def dsoname(name): if name == "[kernel.kallsyms]": return "[kernel]"
@@ -1077,6 +1129,351 @@ class FetchMoreRecordsBar(): self.in_progress = True self.start = self.model.FetchMoreRecords(self.Target()) +# Brance data model level two item + +class BranchLevelTwoItem(): + + def __init__(self, row, text, parent_item): + self.row = row + self.parent_item = parent_item + self.data = [""] * 8 + self.data[7] = text + self.level = 2 + + def getParentItem(self): + return self.parent_item + + def getRow(self): + return self.row + + def childCount(self): + return 0 + + def hasChildren(self): + return False + + def getData(self, column): + return self.data[column] + +# Brance data model level one item + +class BranchLevelOneItem(): + + def __init__(self, glb, row, data, parent_item): + self.glb = glb + self.row = row + self.parent_item = parent_item + self.child_count = 0 + self.child_items = [] + self.data = data[1:] + self.dbid = data[0] + self.level = 1 + self.query_done = False + + def getChildItem(self, row): + return self.child_items[row] + + def getParentItem(self): + return self.parent_item + + def getRow(self): + return self.row + + def Select(self): + self.query_done = True + + if not self.glb.have_disassembler: + return + + query = QSqlQuery(self.glb.db) + + QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip" + " FROM samples" + " INNER JOIN dsos ON samples.to_dso_id = dsos.id" + " INNER JOIN symbols ON samples.to_symbol_id = symbols.id" + " WHERE samples.id = " + str(self.dbid)) + if not query.next(): + return + cpu = query.value(0) + dso = query.value(1) + sym = query.value(2) + if dso == 0 or sym == 0: + return + off = query.value(3) + short_name = query.value(4) + long_name = query.value(5) + build_id = query.value(6) + sym_start = query.value(7) + ip = query.value(8) + + QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start" + " FROM samples" + " INNER JOIN symbols ON samples.symbol_id = symbols.id" + " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) + + " ORDER BY samples.id" + " LIMIT 1") + if not query.next(): + return + if query.value(0) != dso: + # Cannot disassemble from one dso to another + return + bsym = query.value(1) + boff = query.value(2) + bsym_start = query.value(3) + if bsym == 0: + return + tot = bsym_start + boff + 1 - sym_start - off + if tot <= 0 or tot > 16384: + return + + inst = self.glb.disassembler.Instruction() + f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id) + if not f: + return + mode = 0 if Is64Bit(f) else 1 + self.glb.disassembler.SetMode(inst, mode) + + buf_sz = tot + 16 + buf = create_string_buffer(tot + 16) + f.seek(sym_start + off) + buf.value = f.read(buf_sz) + buf_ptr = addressof(buf) + i = 0 + while tot > 0: + cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip) + if cnt: + byte_str = tohex(ip).rjust(16) + for k in xrange(cnt): + byte_str += " %02x" % ord(buf[i]) + i += 1 + while k < 15: + byte_str += " " + k += 1 + self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self)) + self.child_count += 1 + else: + return + buf_ptr += cnt + tot -= cnt + buf_sz -= cnt + ip += cnt + + def childCount(self): + if not self.query_done: + self.Select() + if not self.child_count: + return -1 + return self.child_count + + def hasChildren(self): + if not self.query_done: + return True + return self.child_count > 0 + + def getData(self, column): + return self.data[column] + +# Brance data model root item + +class BranchRootItem(): + + def __init__(self): + self.child_count = 0 + self.child_items = [] + self.level = 0 + + def getChildItem(self, row): + return self.child_items[row] + + def getParentItem(self): + return None + + def getRow(self): + return 0 + + def childCount(self): + return self.child_count + + def hasChildren(self): + return self.child_count > 0 + + def getData(self, column): + return "" + +# Branch data preparation + +def BranchDataPrep(query): + data = [] + for i in xrange(0, 8): + data.append(query.value(i)) + data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) + + " (" + dsoname(query.value(11)) + ")" + " -> " + + tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) + + " (" + dsoname(query.value(15)) + ")") + return data + +# Branch data model + +class BranchModel(TreeModel): + + progress = Signal(object) + + def __init__(self, glb, event_id, where_clause, parent=None): + super(BranchModel, self).__init__(BranchRootItem(), parent) + self.glb = glb + self.event_id = event_id + self.more = True + self.populated = 0 + sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name," + " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END," + " ip, symbols.name, sym_offset, dsos.short_name," + " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name" + " FROM samples" + " INNER JOIN comms ON comm_id = comms.id" + " INNER JOIN threads ON thread_id = threads.id" + " INNER JOIN branch_types ON branch_type = branch_types.id" + " INNER JOIN symbols ON symbol_id = symbols.id" + " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id" + " INNER JOIN dsos ON samples.dso_id = dsos.id" + " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id" + " WHERE samples.id > $$last_id$$" + where_clause + + " AND evsel_id = " + str(self.event_id) + + " ORDER BY samples.id" + " LIMIT " + str(glb_chunk_sz)) + self.fetcher = SQLFetcher(glb, sql, BranchDataPrep, self.AddSample) + self.fetcher.done.connect(self.Update) + self.fetcher.Fetch(glb_chunk_sz) + + def columnCount(self, parent=None): + return 8 + + def columnHeader(self, column): + return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column] + + def columnFont(self, column): + if column != 7: + return None + return QFont("Monospace") + + def DisplayData(self, item, index): + if item.level == 1: + self.FetchIfNeeded(item.row) + return item.getData(index.column()) + + def AddSample(self, data): + child = BranchLevelOneItem(self.glb, self.populated, data, self.root) + self.root.child_items.append(child) + self.populated += 1 + + def Update(self, fetched): + if not fetched: + self.more = False + self.progress.emit(0) + child_count = self.root.child_count + count = self.populated - child_count + if count > 0: + parent = QModelIndex() + self.beginInsertRows(parent, child_count, child_count + count - 1) + self.insertRows(child_count, count, parent) + self.root.child_count += count + self.endInsertRows() + self.progress.emit(self.root.child_count) + + def FetchMoreRecords(self, count): + current = self.root.child_count + if self.more: + self.fetcher.Fetch(count) + else: + self.progress.emit(0) + return current + + def HasMoreRecords(self): + return self.more + +# Branch window + +class BranchWindow(QMdiSubWindow): + + def __init__(self, glb, event_id, name, where_clause, parent=None): + super(BranchWindow, self).__init__(parent) + + model_name = "Branch Events " + str(event_id) + if len(where_clause): + model_name = where_clause + " " + model_name + + self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, where_clause)) + + self.view = QTreeView() + self.view.setUniformRowHeights(True) + self.view.setModel(self.model) + + self.ResizeColumnsToContents() + + self.find_bar = FindBar(self, self, True) + + self.finder = ChildDataItemFinder(self.model.root) + + self.fetch_bar = FetchMoreRecordsBar(self.model, self) + + self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) + + self.setWidget(self.vbox.Widget()) + + AddSubWindow(glb.mainwindow.mdi_area, self, name + " Branch Events") + + def ResizeColumnToContents(self, column, n): + # Using the view's resizeColumnToContents() here is extrememly slow + # so implement a crude alternative + mm = "MM" if column else "MMMM" + font = self.view.font() + metrics = QFontMetrics(font) + max = 0 + for row in xrange(n): + val = self.model.root.child_items[row].data[column] + len = metrics.width(str(val) + mm) + max = len if len > max else max + val = self.model.columnHeader(column) + len = metrics.width(str(val) + mm) + max = len if len > max else max + self.view.setColumnWidth(column, max) + + def ResizeColumnsToContents(self): + n = min(self.model.root.child_count, 100) + if n < 1: + # No data yet, so connect a signal to notify when there is + self.model.rowsInserted.connect(self.UpdateColumnWidths) + return + columns = self.model.columnCount() + for i in xrange(columns): + self.ResizeColumnToContents(i, n) + + def UpdateColumnWidths(self, *x): + # This only needs to be done once, so disconnect the signal now + self.model.rowsInserted.disconnect(self.UpdateColumnWidths) + self.ResizeColumnsToContents() + + def Find(self, value, direction, pattern, context): + self.view.setFocus() + self.find_bar.Busy() + self.finder.Find(value, direction, pattern, context, self.FindDone) + + def FindDone(self, row): + self.find_bar.Idle() + if row >= 0: + self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) + else: + self.find_bar.NotFound() + +# Event list + +def GetEventList(db): + events = [] + query = QSqlQuery(db) + QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id") + while query.next(): + events.append(query.value(0)) + return events + # SQL data preparation def SQLTableDataPrep(query, count):
@@ -1448,6 +1845,8 @@ class MainWindow(QMainWindow): reports_menu = menu.addMenu("&Reports") reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) + self.EventMenu(GetEventList(glb.db), reports_menu) + self.TableMenu(GetTableList(glb), menu) self.window_menu = WindowMenu(self.mdi_area, menu)
@@ -1476,6 +1875,20 @@ class MainWindow(QMainWindow): win = self.mdi_area.activeSubWindow() EnlargeFont(win.view) + def EventMenu(self, events, reports_menu): + branches_events = 0 + for event in events: + event = event.split(":")[0] + if event == "branches": + branches_events += 1 + dbid = 0 + for event in events: + dbid += 1 + event = event.split(":")[0] + if event == "branches": + label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")" + reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self)) + def TableMenu(self, tables, menu): table_menu = menu.addMenu("&Tables") for table in tables:
@@ -1484,9 +1897,112 @@ class MainWindow(QMainWindow): def NewCallGraph(self): CallGraphWindow(self.glb, self) + def NewBranchView(self, event_id): + BranchWindow(self.glb, event_id, "", "", self) + def NewTableView(self, table_name): TableWindow(self.glb, table_name, self) +# XED Disassembler + +class xed_state_t(Structure): + + _fields_ = [ + ("mode", c_int), + ("width", c_int) + ] + +class XEDInstruction(): + + def __init__(self, libxed): + # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion + xedd_t = c_byte * 512 + self.xedd = xedd_t() + self.xedp = addressof(self.xedd) + libxed.xed_decoded_inst_zero(self.xedp) + self.state = xed_state_t() + self.statep = addressof(self.state) + # Buffer for disassembled instruction text + self.buffer = create_string_buffer(256) + self.bufferp = addressof(self.buffer) + +class LibXED(): + + def __init__(self): + self.libxed = CDLL("libxed.so") + + self.xed_tables_init = self.libxed.xed_tables_init + self.xed_tables_init.restype = None + self.xed_tables_init.argtypes = [] + + self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero + self.xed_decoded_inst_zero.restype = None + self.xed_decoded_inst_zero.argtypes = [ c_void_p ] + + self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode + self.xed_operand_values_set_mode.restype = None + self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ] + + self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode + self.xed_decoded_inst_zero_keep_mode.restype = None + self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ] + + self.xed_decode = self.libxed.xed_decode + self.xed_decode.restype = c_int + self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ] + + self.xed_format_context = self.libxed.xed_format_context + self.xed_format_context.restype = c_uint + self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ] + + self.xed_tables_init() + + def Instruction(self): + return XEDInstruction(self) + + def SetMode(self, inst, mode): + if mode: + inst.state.mode = 4 # 32-bit + inst.state.width = 4 # 4 bytes + else: + inst.state.mode = 1 # 64-bit + inst.state.width = 8 # 8 bytes + self.xed_operand_values_set_mode(inst.xedp, inst.statep) + + def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip): + self.xed_decoded_inst_zero_keep_mode(inst.xedp) + err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt) + if err: + return 0, "" + # Use AT&T mode (2), alternative is Intel (3) + ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0) + if not ok: + return 0, "" + # Return instruction length and the disassembled instruction text + # For now, assume the length is in byte 166 + return inst.xedd[166], inst.buffer.value + +def TryOpen(file_name): + try: + return open(file_name, "rb") + except: + return None + +def Is64Bit(f): + result = sizeof(c_void_p) + # ELF support only + pos = f.tell() + f.seek(0) + header = f.read(7) + f.seek(pos) + magic = header[0:4] + eclass = ord(header[4]) + encoding = ord(header[5]) + version = ord(header[6]) + if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1: + result = True if eclass == 2 else False + return result + # Global data class Glb():
@@ -1495,9 +2011,40 @@ class Glb(): self.dbref = dbref self.db = db self.dbname = dbname + self.home_dir = os.path.expanduser("~") + self.buildid_dir = os.getenv("PERF_BUILDID_DIR") + if self.buildid_dir: + self.buildid_dir += "/.build-id/" + else: + self.buildid_dir = self.home_dir + "/.debug/.build-id/" self.app = None self.mainwindow = None self.instances_to_shutdown_on_exit = weakref.WeakSet() + try: + self.disassembler = LibXED() + self.have_disassembler = True + except: + self.have_disassembler = False + + def FileFromBuildId(self, build_id): + file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf" + return TryOpen(file_name) + + def FileFromNamesAndBuildId(self, short_name, long_name, build_id): + # Assume current machine i.e. no support for virtualization + if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore": + file_name = os.getenv("PERF_KCORE") + f = TryOpen(file_name) if file_name else None + if f: + return f + # For now, no special handling if long_name is /proc/kcore + f = TryOpen(long_name) + if f: + return f + f = self.FileFromBuildId(build_id) + if f: + return f + return None def AddInstanceToShutdownOnExit(self, instance): self.instances_to_shutdown_on_exit.add(instance)
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 33/37 perf script: Add --insn-trace for instruction decoding
In reply to Arnaldo Carvalho de MeloFrom: Andi Kleen <[email protected]>
Add a --insn-trace short hand option for decoding and disassembling instruction streams for intel_pt. This automatically pipes the output into the xed disassembler to generate disassembled instructions. This just makes this use model much nicer to use.
Before
% perf record -e intel_pt// ...
% perf script --itrace=i0ns --ns -F +insn,-event,-period | xed -F insn: -A -64
swapper 0 [000] 17276.429606186: ffffffff81010486 pt_config ([kernel.kallsyms]) nopl %eax, (%rax,%rax,1) swapper 0 [000] 17276.429606186: ffffffff8101048b pt_config ([kernel.kallsyms]) add $0x10, %rsp swapper 0 [000] 17276.429606186: ffffffff8101048f pt_config ([kernel.kallsyms]) popq %rbx swapper 0 [000] 17276.429606186: ffffffff81010490 pt_config ([kernel.kallsyms]) popq %rbp swapper 0 [000] 17276.429606186: ffffffff81010491 pt_config ([kernel.kallsyms]) popq %r12 swapper 0 [000] 17276.429606186: ffffffff81010493 pt_config ([kernel.kallsyms]) popq %r13 swapper 0 [000] 17276.429606186: ffffffff81010495 pt_config ([kernel.kallsyms]) popq %r14 swapper 0 [000] 17276.429606186: ffffffff81010497 pt_config ([kernel.kallsyms]) popq %r15 swapper 0 [000] 17276.429606186: ffffffff81010499 pt_config ([kernel.kallsyms]) retq swapper 0 [000] 17276.429606186: ffffffff8101063e pt_event_add ([kernel.kallsyms]) cmpl $0x1, 0x1b0(%rbx) swapper 0 [000] 17276.429606186: ffffffff81010645 pt_event_add ([kernel.kallsyms]) mov $0xffffffea, %eax swapper 0 [000] 17276.429606186: ffffffff8101064a pt_event_add ([kernel.kallsyms]) mov $0x0, %edx swapper 0 [000] 17276.429606186: ffffffff8101064f pt_event_add ([kernel.kallsyms]) popq %rbx swapper 0 [000] 17276.429606186: ffffffff81010650 pt_event_add ([kernel.kallsyms]) cmovnz %edx, %eax swapper 0 [000] 17276.429606186: ffffffff81010653 pt_event_add ([kernel.kallsyms]) jmp 0xffffffff81010635 swapper 0 [000] 17276.429606186: ffffffff81010635 pt_event_add ([kernel.kallsyms]) retq
swapper 0 [000] 17276.429606186: ffffffff8115e687 event_sched_in.isra.107 ([kernel.kallsyms]) test %eax, %eax
Now:
% perf record -e intel_pt// ...
% perf script --insn-trace --xed
... same output ...
XED needs to be installed with:
$ git clone https://github.com/intelxed/mbuild.git mbuild $ git clone https://github.com/intelxed/xed $ cd xed $ ./mfile.py $ ./mfile.py examples $ sudo ./mfile.py --prefix=/usr/local install $ sudo cp obj/examples/xed /usr/local/bin $ xed | head -3 ERROR: required argument(s) were missing Copyright (C) 2017, Intel Corporation. All rights reserved. XED version: [v10.0-328-g7d62c8c49b7b] $
--- tools/perf/Documentation/build-xed.txt | 19 +++++++++++++++++++ tools/perf/Documentation/perf-script.txt | 7 +++++++ tools/perf/builtin-script.c | 23 +++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tools/perf/Documentation/build-xed.txt
tools/perf/Documentation/build-xed.txt new file +19 -0
@@ -0,0 +1,19 @@ + +For --xed the xed tool is needed. Here is how to install it: + + $ git clone https://github.com/intelxed/mbuild.git mbuild + $ git clone https://github.com/intelxed/xed + $ cd xed + $ ./mfile.py --share + $ ./mfile.py examples + $ sudo ./mfile.py --prefix=/usr/local install + $ sudo ldconfig + $ sudo cp obj/examples/xed /usr/local/bin + +Basic xed testing: + + $ xed | head -3 + ERROR: required argument(s) were missing + Copyright (C) 2017, Intel Corporation. All rights reserved. + XED version: [v10.0-328-g7d62c8c49b7b] + $
tools/perf/Documentation/perf-script.txt +7 -0
@@ -383,6 +383,13 @@ include::itrace.txt[] will be printed. Each entry has function name and file/line. Enabled by default, disable with --no-inline. +--insn-trace:: + Show instruction stream for intel_pt traces. Combine with --xed to + show disassembly. + +--xed:: + Run xed disassembler on output. Requires installing the xed disassembler. + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script-perl[1],
tools/perf/builtin-script.c +23 -0
@@ -44,6 +44,7 @@#include <sys/stat.h> #include <fcntl.h> #include <unistd.h> +#include <subcmd/pager.h> #include "sane_ctype.h"
@@ -3103,6 +3104,24 @@ static int perf_script__process_auxtrace_info(struct perf_session *session,#define perf_script__process_auxtrace_info 0#endif +static int parse_insn_trace (const struct option *opt__maybe_unused , +const char *str__maybe_unused , +int unset__maybe_unused ) +{ +parse_output_fields (NULL ,"+insn,-event,-period" ,0 ); +itrace_parse_synth_opts (opt,"i0ns" ,0 ); + nanosecs = true; +return 0 ; +} + +static int parse_xed (const struct option *opt__maybe_unused , +const char *str__maybe_unused , +int unset__maybe_unused ) +{ +force_pager ("xed -F insn: -A -64 | less" ); +return 0 ; +} +int cmd_script (int argc,const char **argv) {bool show_full_info = false;
@@ -3187,6 +3206,10 @@ int cmd_script(int argc, const char **argv) "system-wide collection from all CPUs"),OPT_STRING ('S' ,"symbols" , &symbol_conf.sym_list_str,"symbol[,symbol...]" ,"only consider these symbols" ), +OPT_CALLBACK_OPTARG (0 ,"insn-trace" , &itrace_synth_opts,NULL ,NULL , +"Decode instructions from itrace" , parse_insn_trace), +OPT_CALLBACK_OPTARG (0 ,"xed" ,NULL ,NULL ,NULL , +"Run xed disassembler on output" , parse_xed),OPT_STRING (0 ,"stop-bt" , &symbol_conf.bt_stop_list_str,"symbol[,symbol...]" ,"Stop display of callgraph at these symbols" ), OPT_STRING('C', "cpu", &cpu_list, "cpu", "list of cpus to profile"),
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 34/37 perf script: Make itrace script default to all calls
In reply to Arnaldo Carvalho de MeloFrom: Andi Kleen <[email protected]>
By default 'perf script' for itrace outputs sampled instructions or branches. In my experience this is confusing to users because it's hard to correlate with real program behavior. The sampling makes sense for tools like 'perf report' that actually sample to reduce the run time, but run time is normally not a problem for 'perf script'. It's better to give an accurate representation of the program flow.
Default 'perf script' to output all calls for itrace. That's a much saner default. The old behavior can be still requested with 'perf script' --itrace=ibxwpe100000
v2: Fix ETM build failure
v3: Really fix ETM build failure (Kim Phillips)
--- tools/perf/Documentation/itrace.txt | 7 ++++--- tools/perf/builtin-script.c | 5 ++++- tools/perf/util/auxtrace.c | 17 ++++++++++++----- tools/perf/util/auxtrace.h | 5 ++++- tools/perf/util/cs-etm.c | 3 ++- tools/perf/util/intel-bts.c | 3 ++- tools/perf/util/intel-pt.c | 3 ++- 7 files changed, 30 insertions(+), 13 deletions(-)
tools/perf/Documentation/itrace.txt +4 -3
@@ -11,10 +11,11 @@ l synthesize last branch entries (use with i or x) s skip initial number of events - The default is all events i.e. the same as --itrace=ibxwpe + The default is all events i.e. the same as --itrace=ibxwpe, + except for perf script where it is --itrace=ce - In addition, the period (default 100000) for instructions events - can be specified in units of: + In addition, the period (default 100000, except for perf script where it is 1) + for instructions events can be specified in units of: i instructions t ticks
tools/perf/builtin-script.c +4 -1
@@ -3131,7 +3131,10 @@ int cmd_script(int argc, const char **argv)char *rec_script_path =NULL ;char *rep_script_path =NULL ;struct perf_session *session; -struct itrace_synth_opts itrace_synth_opts = { .set = false, }; +struct itrace_synth_opts itrace_synth_opts = { + .set = false, + .default_no_sample = true, + };char *script_path =NULL ;const char **__argv;int i, j, err =0 ;
tools/perf/util/auxtrace.c +12 -5
@@ -962,16 +962,23 @@ s64 perf_event__process_auxtrace(struct perf_session *session,#define PERF_ITRACE_DEFAULT_LAST_BRANCH_SZ 64#define PERF_ITRACE_MAX_LAST_BRANCH_SZ 1024 -void itrace_synth_opts__set_default (struct itrace_synth_opts *synth_opts) +void itrace_synth_opts__set_default (struct itrace_synth_opts *synth_opts, +bool no_sample) { - synth_opts->instructions = true; synth_opts->branches = true; synth_opts->transactions = true; synth_opts->ptwrites = true; synth_opts->pwr_events = true; synth_opts->errors = true; - synth_opts->period_type =PERF_ITRACE_DEFAULT_PERIOD_TYPE ; - synth_opts->period =PERF_ITRACE_DEFAULT_PERIOD ; +if (no_sample) { + synth_opts->period_type =PERF_ITRACE_PERIOD_INSTRUCTIONS ; + synth_opts->period =1 ; + synth_opts->calls = true; + }else { + synth_opts->instructions = true; + synth_opts->period_type =PERF_ITRACE_DEFAULT_PERIOD_TYPE ; + synth_opts->period =PERF_ITRACE_DEFAULT_PERIOD ; + } synth_opts->callchain_sz =PERF_ITRACE_DEFAULT_CALLCHAIN_SZ ; synth_opts->last_branch_sz =PERF_ITRACE_DEFAULT_LAST_BRANCH_SZ ; synth_opts->initial_skip =0 ;
@@ -999,7 +1006,7 @@ int itrace_parse_synth_opts(const struct option *opt, const char *str, }if (!str) { -itrace_synth_opts__set_default (synth_opts); +itrace_synth_opts__set_default (synth_opts, false);return 0 ; }
tools/perf/util/auxtrace.h +4 -1
@@ -58,6 +58,7 @@ enum itrace_period_type {/** * struct itrace_synth_opts - AUX area tracing synthesis options. * @set: indicates whether or not options have been set +* @default_no_sample: Default to no sampling. * @inject: indicates the event (not just the sample) must be fully synthesized * because 'perf inject' will write it out * @instructions: whether to synthesize 'instructions' events
@@ -82,6 +83,7 @@ enum itrace_period_type {*/ struct itrace_synth_opts {bool set; +bool default_no_sample;bool inject;bool instructions;bool branches;
@@ -528,7 +530,8 @@ int perf_event__process_auxtrace_error(struct perf_session *session, union perf_event *event);int itrace_parse_synth_opts (const struct option *opt,const char *str,int unset); -void itrace_synth_opts__set_default (struct itrace_synth_opts *synth_opts); +void itrace_synth_opts__set_default (struct itrace_synth_opts *synth_opts, +bool no_sample);size_t perf_event__fprintf_auxtrace_error (union perf_event *event,FILE *fp); void perf_session__auxtrace_error_inc(struct perf_session *session,
tools/perf/util/cs-etm.c +2 -1
@@ -1432,7 +1432,8 @@ int cs_etm__process_auxtrace_info(union perf_event *event,if (session->itrace_synth_opts && session->itrace_synth_opts->set) { etm->synth_opts = *session->itrace_synth_opts; }else { -itrace_synth_opts__set_default (&etm->synth_opts); +itrace_synth_opts__set_default (&etm->synth_opts, + session->itrace_synth_opts->default_no_sample); etm->synth_opts.callchain = false; }
tools/perf/util/intel-bts.c +2 -1
@@ -910,7 +910,8 @@ int intel_bts_process_auxtrace_info(union perf_event *event,if (session->itrace_synth_opts && session->itrace_synth_opts->set) { bts->synth_opts = *session->itrace_synth_opts; }else { -itrace_synth_opts__set_default (&bts->synth_opts); +itrace_synth_opts__set_default (&bts->synth_opts, + session->itrace_synth_opts->default_no_sample);if (session->itrace_synth_opts) bts->synth_opts.thread_stack = session->itrace_synth_opts->thread_stack;
tools/perf/util/intel-pt.c +2 -1
@@ -2559,7 +2559,8 @@ int intel_pt_process_auxtrace_info(union perf_event *event,if (session->itrace_synth_opts && session->itrace_synth_opts->set) { pt->synth_opts = *session->itrace_synth_opts; }else { -itrace_synth_opts__set_default (&pt->synth_opts); +itrace_synth_opts__set_default (&pt->synth_opts, + session->itrace_synth_opts->default_no_sample);if (use_browser !=-1 ) { pt->synth_opts.branches = false; pt->synth_opts.callchain = true;
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 35/37 tools script: Add --call-trace and --call-ret-trace
In reply to Arnaldo Carvalho de MeloFrom: Andi Kleen <[email protected]>
Add short cut options to print PT call trace and call-ret-trace, for calls and call and returns. Roughly corresponds to ftrace function tracer and function graph tracer.
Just makes these common use cases nicer to use.
% perf record -a -e intel_pt// sleep 1
% perf script --call-trace
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_pmu_enable
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) __x86_indirect_thunk_rax
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) event_filter_match
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) group_sched_in
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) __x86_indirect_thunk_rax
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) event_sched_in.isra.107
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_event_set_state.part.71
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_event_update_time
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_pmu_disable
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_log_itrace_start
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) __x86_indirect_thunk_rax
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_event_update_userpage% perf script --call-ret-trace
perf 900 [000] 194167.205652203: tr strt ([unknown]) pt_config
perf 900 [000] 194167.205652203: return ([kernel.kallsyms]) pt_config
perf 900 [000] 194167.205652203: return ([kernel.kallsyms]) pt_event_add
perf 900 [000] 194167.205652203: call ([kernel.kallsyms]) perf_pmu_enable
perf 900 [000] 194167.205652203: return ([kernel.kallsyms]) perf_pmu_nop_void
perf 900 [000] 194167.205652203: return ([kernel.kallsyms]) event_sched_in.isra.107
perf 900 [000] 194167.205652203: call ([kernel.kallsyms]) __x86_indirect_thunk_rax
perf 900 [000] 194167.205652203: return ([kernel.kallsyms]) perf_pmu_nop_int
perf 900 [000] 194167.205652203: return ([kernel.kallsyms]) group_sched_in
perf 900 [000] 194167.205652203: call ([kernel.kallsyms]) event_filter_match
perf 900 [000] 194167.205652203: return ([kernel.kallsyms]) event_filter_match
perf 900 [000] 194167.205652203: call ([kernel.kallsyms]) group_sched_in
perf 900 [000] 194167.205652203: call ([kernel.kallsyms]) __x86_indirect_thunk_rax
perf 900 [000] 194167.205652203: return ([kernel.kallsyms]) perf_pmu_nop_txn
perf 900 [000] 194167.205652203: call ([kernel.kallsyms]) event_sched_in.isra.107
perf 900 [000] 194167.205652203: call ([kernel.kallsyms]) perf_event_set_state.part.71--- tools/perf/Documentation/perf-script.txt | 7 +++++++ tools/perf/builtin-script.c | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+)
tools/perf/Documentation/perf-script.txt +7 -0
@@ -390,6 +390,13 @@ include::itrace.txt[] --xed:: Run xed disassembler on output. Requires installing the xed disassembler. +--call-trace:: + Show call stream for intel_pt traces. The CPUs are interleaved, but + can be filtered with -C. + +--call-ret-trace:: + Show call and return stream for intel_pt traces. + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script-perl[1],
tools/perf/builtin-script.c +24 -0
@@ -3122,6 +3122,26 @@ static int parse_xed(const struct option *opt __maybe_unused,return 0 ; } +static int parse_call_trace (const struct option *opt__maybe_unused , +const char *str__maybe_unused , +int unset__maybe_unused ) +{ +parse_output_fields (NULL ,"-ip,-addr,-event,-period,+callindent" ,0 ); +itrace_parse_synth_opts (opt,"cewp" ,0 ); + nanosecs = true; +return 0 ; +} + +static int parse_callret_trace (const struct option *opt__maybe_unused , +const char *str__maybe_unused , +int unset__maybe_unused ) +{ +parse_output_fields (NULL ,"-ip,-addr,-event,-period,+callindent,+flags" ,0 ); +itrace_parse_synth_opts (opt,"crewp" ,0 ); + nanosecs = true; +return 0 ; +} +int cmd_script (int argc,const char **argv) {bool show_full_info = false;
@@ -3213,6 +3233,10 @@ int cmd_script(int argc, const char **argv) "Decode instructions from itrace", parse_insn_trace),OPT_CALLBACK_OPTARG (0 ,"xed" ,NULL ,NULL ,NULL ,"Run xed disassembler on output" , parse_xed), +OPT_CALLBACK_OPTARG (0 ,"call-trace" , &itrace_synth_opts,NULL ,NULL , +"Decode calls from from itrace" , parse_call_trace), +OPT_CALLBACK_OPTARG (0 ,"call-ret-trace" , &itrace_synth_opts,NULL ,NULL , +"Decode calls and returns from itrace" , parse_callret_trace),OPT_STRING (0 ,"stop-bt" , &symbol_conf.bt_stop_list_str,"symbol[,symbol...]" ,"Stop display of callgraph at these symbols" ), OPT_STRING('C', "cpu", &cpu_list, "cpu", "list of cpus to profile"),
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 36/37 perf script: Implement --graph-function
In reply to Arnaldo Carvalho de MeloFrom: Andi Kleen <[email protected]>
Add a ftrace style --graph-function argument to 'perf script' that allows to print itrace function calls only below a given function. This makes it easier to find the code of interest in a large trace.
% perf record -e intel_pt//k -a sleep 1
% perf script --graph-function group_sched_in --call-trace
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) group_sched_in
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) __x86_indirect_thunk_rax
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) event_sched_in.isra.107
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_event_set_state.part.71
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_event_update_time
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_pmu_disable
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_log_itrace_start
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) __x86_indirect_thunk_rax
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_event_update_userpage
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) calc_timer_values
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) sched_clock_cpu
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) __x86_indirect_thunk_rax
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) arch_perf_update_userpage
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) __fentry__
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) using_native_sched_clock
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) sched_clock_stable
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) perf_pmu_enable
perf 900 [000] 194167.205652203: ([kernel.kallsyms]) __x86_indirect_thunk_rax
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) group_sched_in
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) __x86_indirect_thunk_rax
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) event_sched_in.isra.107
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) perf_event_set_state.part.71
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) perf_event_update_time
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) perf_pmu_disable
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) perf_log_itrace_start
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) __x86_indirect_thunk_rax
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) perf_event_update_userpage
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) calc_timer_values
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) sched_clock_cpu
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) __x86_indirect_thunk_rax
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) arch_perf_update_userpage
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) __fentry__
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) using_native_sched_clock
swapper 0 [001] 194167.205660693: ([kernel.kallsyms]) sched_clock_stable--- tools/perf/Documentation/perf-script.txt | 4 ++ tools/perf/builtin-script.c | 96 ++++++++++++++++++++++++++------ tools/perf/util/symbol.h | 3 +- tools/perf/util/thread.h | 2 + 4 files changed, 86 insertions(+), 19 deletions(-)
tools/perf/Documentation/perf-script.txt +4 -0
@@ -397,6 +397,10 @@ include::itrace.txt[] --call-ret-trace:: Show call and return stream for intel_pt traces. +--graph-function:: + For itrace only show specified functions and their callees for + itrace. Multiple functions can be separated by comma. + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script-perl[1],
tools/perf/builtin-script.c +78 -18
138 lines not on this page · raw message
tools/perf/util/symbol.h +2 -1
@@ -123,7 +123,8 @@ struct symbol_conf {const char *vmlinux_name, *kallsyms_name, *source_prefix, - *field_sep; + *field_sep, + *graph_function;const char *default_guest_vmlinux_name, *default_guest_kallsyms, *default_guest_modules;
tools/perf/util/thread.h +2 -0
@@ -42,6 +42,8 @@ struct thread {void *addr_space;struct unwind_libunwind_ops *unwind_libunwind_ops;#endif +bool filter; +int filter_entry_depth; };struct machine ;
Signature · 1 line
2.14.4
Arnaldo Carvalho de Melo
maintainerPATCH 37/37 perf script: Support total cycles count
In reply to Arnaldo Carvalho de MeloFrom: Andi Kleen <[email protected]>
For 'perf script' brstackinsn also print a running cycles count. This makes it easier to calculate cycle deltas for code sections measured with LBRs.
% perf record -b -a sleep 1
% perf script -F +brstackinsn
...
00007f73ecc41083 insn: 74 06 # PRED 9 cycles [17] 1.11 IPC
00007f73ecc4108b insn: a8 10
00007f73ecc4108d insn: 74 71 # PRED 1 cycles [18] 1.00 IPC
00007f73ecc41100 insn: 48 8b 46 10
00007f73ecc41104 insn: 4c 8b 38
00007f73ecc41107 insn: 4d 85 ff
00007f73ecc4110a insn: 0f 84 b0 00 00 00
00007f73ecc41110 insn: 83 43 58 01
00007f73ecc41114 insn: 48 89 df
00007f73ecc41117 insn: e8 94 73 04 00 # PRED 6 cycles [24] 1.00 IPC--- tools/perf/builtin-script.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)
tools/perf/builtin-script.c +7 -4
@@ -913,7 +913,7 @@ static int grab_bb(u8 *buffer, u64 start, u64 end,static int ip__fprintf_jump (uint64_t ip,struct branch_entry *en,struct perf_insn *x,u8 *inbuf,int len, -int insn,FILE *fp) +int insn,FILE *fp,int *total_cycles) { int printed = fprintf(fp, "\t%016" PRIx64 "\t%-30s\t#%s%s%s%s", ip, dump_insn(x, ip, inbuf, len, NULL),
@@ -922,7 +922,8 @@ static int ip__fprintf_jump(uint64_t ip, struct branch_entry *en, en->flags.in_tx ?" INTX" :"" , en->flags.abort ? " ABORT" : "");if (en->flags.cycles) { - printed +=fprintf (fp," %d cycles" , en->flags.cycles); + *total_cycles += en->flags.cycles; + printed +=fprintf (fp," %d cycles [%d]" , en->flags.cycles, *total_cycles);if (insn) printed +=fprintf (fp," %.2f IPC" , (float )insn / en->flags.cycles); }
@@ -979,6 +980,7 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample,u8 buffer[MAXBB ];unsigned off;struct symbol *lastsym =NULL ; +int total_cycles =0 ;if (!(br && br->nr))return 0 ;
@@ -999,7 +1001,7 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample, printed +=ip__fprintf_sym (br->entries[nr -1 ].from, thread, x.cpumode, x.cpu, &lastsym, attr, fp); printed +=ip__fprintf_jump (br->entries[nr -1 ].from, &br->entries[nr -1 ], - &x, buffer, len,0 , fp); + &x, buffer, len,0 , fp, &total_cycles); }/* Print all blocks */
@@ -1027,7 +1029,8 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample, printed +=ip__fprintf_sym (ip, thread, x.cpumode, x.cpu, &lastsym, attr, fp);if (ip == end) { - printed +=ip__fprintf_jump (ip, &br->entries[i], &x, buffer + off, len - off, insn, fp); + printed +=ip__fprintf_jump (ip, &br->entries[i], &x, buffer + off, len - off, insn, fp, + &total_cycles);break ; }else { printed += fprintf(fp, "\t%016" PRIx64 "\t%s\n", ip,
Signature · 1 line
2.14.4
Quoted 157 lines · Arnaldo Carvalho de Melo
Hi Ingo,
Please consider pulling,
- Arnaldo
Test results at the end of this message, as usual.
The following changes since commit
ce6c9da1113ad681bff27a3e376d2017f7f8a59c:Merge remote-tracking branch 'tip/perf/urgent' into perf/core (2018-10-18 11:13:01 -0300)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux.gittags/perf-core-for-mingo-4.20-20181025for you to fetch changes up to
fe57120e18a1f9124ca758c89cc54f91333d1847:perf script: Support total cycles count (2018-10-24 15:29:56 -0300)
----------------------------------------------------------------
perf/core improvements and fixes:- Introduce 'perf trace --max-events' for stopping 'perf trace' when that many syscalls (enter+exit), tracepoints or other events such as page faults take place.
Support that as well on a per-event basis, e.g.:
perf trace -e sched:*switch/nr=2/,block:*_plug/nr=4/,block:*_unplug/nr=1/,net:*dev_queue/nr=3,max-stack=16/Will stop when 2 context switches, 4 block plugs, 1 block unplug and 3
net_dev_queuetracepoints take place. (Arnaldo Carvalho de Melo)- Poll for monitored tasks being alive in 'perf stat -p/-t', exiting when those tasks all terminate (Jiri Olsa)
- Encode -k clockid frequency into perf.data to enable timestamps derived metrics conversion into wall clock time on reporting stage. (Alexey Budankov)
- Improve Intel PT call graph from SQL database and GUI python scripts, including adopting the Qt MDI interface to allow for multiple subwindows for all the tables, helping in better visualizing the data in the SQL tables, also uses, when available, the Intel XED disassembler libraries to present the Intel PT data as x86 asm mnemonics. This last feature is not currently working in some cases, fix is being discussed (Adrian Hunter)
- Implement a ftrace
function_graphview in 'perf script' when processing hardware trace data such as Intel PT (Andi Kleen)- Better integration with the Intel XED disassembler, when available, in 'perf script' (Andi Kleen)
- Some 'perf trace' drop refcount fixes (Arnaldo Carvalho de Melo)
- Add Sparc support to 'perf annotate', jitdump (David Miller)
- Fix PLT symbols entry/header sizes properly on Sparc (David Miller)
- Fix generation of system call table failure with /tmp mounted with 'noexec' in arm64 (Hongxu Jia)
- Allow extended console debug output in 'perf script' (Milian Wolff)
- Flush output stream after events in 'perf script' verbose mode (Milian Wolff)
Signed-off-by Arnaldo Carvalho de Melo <[email protected]>----------------------------------------------------------------
Adrian Hunter (17):
perf scripts python:call-graph-from-sql.py: Use SPDX license identifier perf scripts python:call-graph-from-sql.py: Provide better default column sizes perf scripts python:call-graph-from-sql.py: Set a minimum window size
perf scripts python:call-graph-from-sql.py: Change icon
perf scripts python:call-graph-from-sql.py: Make a "Main" function
perf scripts python:call-graph-from-sql.py: Separate the database details into a class perf scripts python:call-graph-from-sql.py: Add a class for global data perf scripts python:call-graph-from-sql.py: Remove use ofsetObjectName()perf scripts python:call-graph-from-sql.py: Factor out CallGraphModel from TreeModel perf scripts python:call-graph-from-sql.py: Add data helper functions
perf scripts python:call-graph-from-sql.py: Refactor TreeItem class
perf scripts python:call-graph-from-sql.py: Rename toexported-sql-viewer.pyperf scripts python:exported-sql-viewer.py: Add support for multiple sub-windows perf scripts python:exported-sql-viewer.py: Add ability to find symbols in the call-graph perf scripts python:exported-sql-viewer.py: Add ability to shrink / enlarge font perf scripts python:exported-sql-viewer.py: Add ability to display all the database tables perf scripts python:exported-sql-viewer.py: Add All branches reportAlexey Budankov (1):
perf record: Encode -k clockid frequency into Perf traceAndi Kleen (5):
perf script: Add --insn-trace for instruction decoding perf script: Make itrace script default to all calls tools script: Add --call-trace and --call-ret-trace perf script: Implement --graph-function
perf script: Support total cycles countArnaldo Carvalho de Melo (7):
tools lib subcmd: IntroduceOPTION_ULONG
perf trace: Introduce --max-events
perf evsel: Introduce per eventmax_eventsproperty
perf evsel: Mark a evsel as disabled when asking the kernel do disable it perf trace: Dropaddr_locationrefcounts
perf trace: Drop thread refcount intrace__event_handler()perf trace: Introduce per-event maximum number of events propertyDavid Miller (3):
perf annotate: Add Sparc support
perf jitdump: Add Sparc support.
perf symbols: Set PLT entry/header sizes properly on SparcHongxu Jia (1):
perf arm64: Fix generate system call table failed with /tmp mounted with noexecJiri Olsa (1):
perf stat: Poll for monitored tasks being aliveMilian Wolff (2):
perf script: Allow extended console debug output perf script: Flush output stream after events in verbose modetools/lib/subcmd/parse-options.c | 19 + tools/lib/subcmd/parse-options.h | 2 + tools/perf/Documentation/build-xed.txt | 19 + tools/perf/Documentation/intel-pt.txt | 2 +- tools/perf/Documentation/itrace.txt | 7 +- tools/perf/Documentation/perf-script.txt | 18 + tools/perf/Documentation/perf-trace.txt | 67 + tools/perf/arch/arm64/entry/syscalls/mksyscalltbl | 2 +- tools/perf/arch/sparc/Makefile | 2 + tools/perf/arch/sparc/annotate/instructions.c | 169 ++ tools/perf/builtin-record.c | 24 +- tools/perf/builtin-script.c | 166 +- tools/perf/builtin-stat.c | 24 + tools/perf/builtin-trace.c | 50 +- tools/perf/perf.h | 1 + tools/perf/scripts/python/call-graph-from-sql.py | 339 ---- tools/perf/scripts/python/export-to-postgresql.py | 2 +- tools/perf/scripts/python/export-to-sqlite.py | 2 +- tools/perf/scripts/python/exported-sql-viewer.py | 2128 +++++++++++++++++++++ tools/perf/util/annotate.c | 8 + tools/perf/util/auxtrace.c | 17 +- tools/perf/util/auxtrace.h | 5 +- tools/perf/util/cs-etm.c | 3 +- tools/perf/util/env.h | 1 + tools/perf/util/evlist.c | 2 +- tools/perf/util/evsel.c | 27 +- tools/perf/util/evsel.h | 5 + tools/perf/util/genelf.h | 6 + tools/perf/util/header.c | 23 + tools/perf/util/header.h | 1 + tools/perf/util/intel-bts.c | 3 +- tools/perf/util/intel-pt.c | 3 +- tools/perf/util/parse-events.c | 8 + tools/perf/util/parse-events.h | 1 + tools/perf/util/parse-events.l | 1 +
Quoted 8 lines
tools/perf/util/symbol-elf.c | 12 +- tools/perf/util/symbol.h | 3 +- tools/perf/util/thread.h | 2 + 38 files changed, 2778 insertions(+), 396 deletions(-) create mode 100644 tools/perf/Documentation/build-xed.txt create mode 100644 tools/perf/arch/sparc/annotate/instructions.c delete mode 100644 tools/perf/scripts/python/call-graph-from-sql.py create mode 100755 tools/perf/scripts/python/exported-sql-viewer.py
Pulled, thanks a lot Arnaldo!
Ingo