From 916d4092a1d2d7bb50630497be71ee4c4c2807fa Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Wed, 18 Nov 2015 17:38:49 -0300 Subject: [PATCH 01/37] perf test: Fix build of BPF and LLVM on older glibc libraries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $ rpm -q glibc glibc-2.12-1.166.el6_7.1.x86_64 CC /tmp/build/perf/tests/llvm.o cc1: warnings being treated as errors tests/llvm.c: In function ‘test_llvm__fetch_bpf_obj’: tests/llvm.c:53: error: declaration of ‘index’ shadows a global declaration /usr/include/string.h:489: error: shadowed declaration is here CC /tmp/build/perf/tests/bpf.o cc1: warnings being treated as errors tests/bpf.c: In function ‘__test__bpf’: tests/bpf.c:149: error: declaration of ‘index’ shadows a global declaration /usr/include/string.h:489: error: shadowed declaration is here Cc: He Kuang Cc: Jiri Olsa Cc: Namhyung Kim Cc: pi3orama@163.com Cc: Wang Nan Cc: Zefan Li Fixes: b31de018a628 ("perf test: Enhance the LLVM test: update basic BPF test program") Fixes: ba1fae431e74 ("perf test: Add 'perf test BPF'") Link: http://lkml.kernel.org/n/tip-akpo4r750oya2phxoh9e3447@git.kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/bpf.c | 14 +++++++------- tools/perf/tests/llvm.c | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tools/perf/tests/bpf.c b/tools/perf/tests/bpf.c index ec16f7812c8b..6ebfdee3e2c6 100644 --- a/tools/perf/tests/bpf.c +++ b/tools/perf/tests/bpf.c @@ -146,7 +146,7 @@ prepare_bpf(void *obj_buf, size_t obj_buf_sz, const char *name) return obj; } -static int __test__bpf(int index) +static int __test__bpf(int idx) { int ret; void *obj_buf; @@ -154,27 +154,27 @@ static int __test__bpf(int index) struct bpf_object *obj; ret = test_llvm__fetch_bpf_obj(&obj_buf, &obj_buf_sz, - bpf_testcase_table[index].prog_id, + bpf_testcase_table[idx].prog_id, true); if (ret != TEST_OK || !obj_buf || !obj_buf_sz) { pr_debug("Unable to get BPF object, %s\n", - bpf_testcase_table[index].msg_compile_fail); - if (index == 0) + bpf_testcase_table[idx].msg_compile_fail); + if (idx == 0) return TEST_SKIP; else return TEST_FAIL; } obj = prepare_bpf(obj_buf, obj_buf_sz, - bpf_testcase_table[index].name); + bpf_testcase_table[idx].name); if (!obj) { ret = TEST_FAIL; goto out; } ret = do_test(obj, - bpf_testcase_table[index].target_func, - bpf_testcase_table[index].expect_result); + bpf_testcase_table[idx].target_func, + bpf_testcase_table[idx].expect_result); out: bpf__clear(); return ret; diff --git a/tools/perf/tests/llvm.c b/tools/perf/tests/llvm.c index bc4cf507cde5..366e38ba8b49 100644 --- a/tools/perf/tests/llvm.c +++ b/tools/perf/tests/llvm.c @@ -50,7 +50,7 @@ static struct { int test_llvm__fetch_bpf_obj(void **p_obj_buf, size_t *p_obj_buf_sz, - enum test_llvm__testcase index, + enum test_llvm__testcase idx, bool force) { const char *source; @@ -59,11 +59,11 @@ test_llvm__fetch_bpf_obj(void **p_obj_buf, char *tmpl_new = NULL, *clang_opt_new = NULL; int err, old_verbose, ret = TEST_FAIL; - if (index >= __LLVM_TESTCASE_MAX) + if (idx >= __LLVM_TESTCASE_MAX) return TEST_FAIL; - source = bpf_source_table[index].source; - desc = bpf_source_table[index].desc; + source = bpf_source_table[idx].source; + desc = bpf_source_table[idx].desc; perf_config(perf_config_cb, NULL); From 9a13c6587e2f0d5e80ce02f5f9ef62788b48d163 Mon Sep 17 00:00:00 2001 From: Kevin Hilman Date: Tue, 17 Nov 2015 13:54:19 -0800 Subject: [PATCH 02/37] tools: Fix selftests_install Makefile rule Fix copy/paste error in selftests_install rule which was copy-pasted from the clean rule but not properly changed. Signed-off-by: Kevin Hilman Cc: Bamvor Jian Zhang Cc: Jiri Olsa Cc: Jonathan Cameron Cc: Michael Ellerman Cc: Pali Rohar Cc: Pavel Machek Cc: Roberta Dobrescu Cc: Shuah Khan Cc: linaro-kernel@lists.linaro.org Link: http://lkml.kernel.org/r/1447797261-1775-1-git-send-email-khilman@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Makefile b/tools/Makefile index 7dc820a8c1f1..0ba0df3b516f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -96,7 +96,7 @@ cgroup_install firewire_install hv_install lguest_install perf_install usb_insta $(call descend,$(@:_install=),install) selftests_install: - $(call descend,testing/$(@:_clean=),install) + $(call descend,testing/$(@:_install=),install) turbostat_install x86_energy_perf_policy_install: $(call descend,power/x86/$(@:_install=),install) From 4ddd32741da87657113d964588ce13ee64b34820 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Mon, 16 Nov 2015 11:36:29 -0300 Subject: [PATCH 03/37] tools: Adopt memdup() from tools/perf, moving it to tools/lib/string.c That will contain more string functions with counterparts, sometimes verbatim copies, in the kernel. Acked-by: Wang Nan Cc: Adrian Hunter Cc: Alexey Dobriyan Cc: David Ahern Cc: Jiri Olsa Cc: Namhyung Kim Link: http://lkml.kernel.org/n/tip-rah6g97kn21vfgmlramorz6o@git.kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/include/linux/string.h | 9 +++++++++ tools/lib/string.c | 19 +++++++++++++++++++ tools/perf/MANIFEST | 2 ++ tools/perf/util/Build | 6 ++++++ tools/perf/util/include/linux/string.h | 3 --- tools/perf/util/string.c | 16 ---------------- 6 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 tools/include/linux/string.h create mode 100644 tools/lib/string.c delete mode 100644 tools/perf/util/include/linux/string.h diff --git a/tools/include/linux/string.h b/tools/include/linux/string.h new file mode 100644 index 000000000000..f3a6db6ad732 --- /dev/null +++ b/tools/include/linux/string.h @@ -0,0 +1,9 @@ +#ifndef _TOOLS_LINUX_STRING_H_ +#define _TOOLS_LINUX_STRING_H_ + + +#include /* for size_t */ + +void *memdup(const void *src, size_t len); + +#endif /* _LINUX_STRING_H_ */ diff --git a/tools/lib/string.c b/tools/lib/string.c new file mode 100644 index 000000000000..ecfd43a9b24e --- /dev/null +++ b/tools/lib/string.c @@ -0,0 +1,19 @@ +#include +#include +#include + +/** + * memdup - duplicate region of memory + * + * @src: memory region to duplicate + * @len: memory region length + */ +void *memdup(const void *src, size_t len) +{ + void *p = malloc(len); + + if (p) + memcpy(p, src, len); + + return p; +} diff --git a/tools/perf/MANIFEST b/tools/perf/MANIFEST index 39c38cb45b00..2562eac6451d 100644 --- a/tools/perf/MANIFEST +++ b/tools/perf/MANIFEST @@ -22,6 +22,7 @@ tools/lib/api tools/lib/bpf tools/lib/hweight.c tools/lib/rbtree.c +tools/lib/string.c tools/lib/symbol/kallsyms.c tools/lib/symbol/kallsyms.h tools/lib/util/find_next_bit.c @@ -50,6 +51,7 @@ tools/include/linux/log2.h tools/include/linux/poison.h tools/include/linux/rbtree.h tools/include/linux/rbtree_augmented.h +tools/include/linux/string.h tools/include/linux/types.h tools/include/linux/err.h include/asm-generic/bitops/arch_hweight.h diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 591b3fe3ed49..e2316900f96f 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -21,6 +21,7 @@ libperf-y += parse-events.o libperf-y += perf_regs.o libperf-y += path.o libperf-y += rbtree.o +libperf-y += libstring.o libperf-y += bitmap.o libperf-y += hweight.o libperf-y += run-command.o @@ -138,6 +139,7 @@ $(OUTPUT)util/pmu.o: $(OUTPUT)util/pmu-flex.c $(OUTPUT)util/pmu-bison.c CFLAGS_find_next_bit.o += -Wno-unused-parameter -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))" CFLAGS_rbtree.o += -Wno-unused-parameter -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))" +CFLAGS_libstring.o += -Wno-unused-parameter -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))" CFLAGS_hweight.o += -Wno-unused-parameter -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))" CFLAGS_parse-events.o += -Wno-redundant-decls @@ -153,6 +155,10 @@ $(OUTPUT)util/rbtree.o: ../lib/rbtree.c FORCE $(call rule_mkdir) $(call if_changed_dep,cc_o_c) +$(OUTPUT)util/libstring.o: ../lib/string.c FORCE + $(call rule_mkdir) + $(call if_changed_dep,cc_o_c) + $(OUTPUT)util/hweight.o: ../lib/hweight.c FORCE $(call rule_mkdir) $(call if_changed_dep,cc_o_c) diff --git a/tools/perf/util/include/linux/string.h b/tools/perf/util/include/linux/string.h deleted file mode 100644 index 6f19c548ecc0..000000000000 --- a/tools/perf/util/include/linux/string.h +++ /dev/null @@ -1,3 +0,0 @@ -#include - -void *memdup(const void *src, size_t len); diff --git a/tools/perf/util/string.c b/tools/perf/util/string.c index fc8781de62db..7f7e072be746 100644 --- a/tools/perf/util/string.c +++ b/tools/perf/util/string.c @@ -342,22 +342,6 @@ char *rtrim(char *s) return s; } -/** - * memdup - duplicate region of memory - * @src: memory region to duplicate - * @len: memory region length - */ -void *memdup(const void *src, size_t len) -{ - void *p; - - p = malloc(len); - if (p) - memcpy(p, src, len); - - return p; -} - char *asprintf_expr_inout_ints(const char *var, bool in, size_t nints, int *ints) { /* From 7d85c434214ea0b3416f7a62f76a0785b00d8797 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 11:42:05 -0300 Subject: [PATCH 04/37] tools: Clone the kernel's strtobool function Copying it to tools/lib/string.c, the counterpart to the kernel's lib/string.c. This is preparation for enhancing BPF program configuration, which will allow config string like 'inlines=yes'. Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Jonathan Cameron Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-6-git-send-email-wangnan0@huawei.com [ Copied it to tools/lib/string.c instead, to make it usable by other tools/ ] Signed-off-by: Arnaldo Carvalho de Melo --- tools/include/linux/string.h | 2 ++ tools/lib/string.c | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/tools/include/linux/string.h b/tools/include/linux/string.h index f3a6db6ad732..2e2f736c039c 100644 --- a/tools/include/linux/string.h +++ b/tools/include/linux/string.h @@ -6,4 +6,6 @@ void *memdup(const void *src, size_t len); +int strtobool(const char *s, bool *res); + #endif /* _LINUX_STRING_H_ */ diff --git a/tools/lib/string.c b/tools/lib/string.c index ecfd43a9b24e..065e54f42d8f 100644 --- a/tools/lib/string.c +++ b/tools/lib/string.c @@ -1,5 +1,20 @@ +/* + * linux/tools/lib/string.c + * + * Copied from linux/lib/string.c, where it is: + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * More specifically, the first copied function was strtobool, which + * was introduced by: + * + * d0f1fed29e6e ("Add a strtobool function matching semantics of existing in kernel equivalents") + * Author: Jonathan Cameron + */ + #include #include +#include #include /** @@ -17,3 +32,31 @@ void *memdup(const void *src, size_t len) return p; } + +/** + * strtobool - convert common user inputs into boolean values + * @s: input string + * @res: result + * + * This routine returns 0 iff the first character is one of 'Yy1Nn0'. + * Otherwise it will return -EINVAL. Value pointed to by res is + * updated upon finding a match. + */ +int strtobool(const char *s, bool *res) +{ + switch (s[0]) { + case 'y': + case 'Y': + case '1': + *res = true; + break; + case 'n': + case 'N': + case '0': + *res = false; + break; + default: + return -EINVAL; + } + return 0; +} From b580563e38487d9db8e94080149644da71c533c1 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 12:10:09 +0000 Subject: [PATCH 05/37] bpf tools: Load a program with different instances using preprocessor This patch is a preparation for BPF prologue support which allows generating a series of BPF bytecode for fetching kernel data before calling program code. With the newly introduced multiple instances support, perf is able to create different prologues for different kprobe points. Before this patch, a bpf_program can be loaded into kernel only once, and get the only resulting fd. What this patch does is to allow creating and loading different variants of one bpf_program, then fetching their fds. Here we describe the basic idea in this patch. The detailed description of the newly introduced APIs can be found in comments in the patch body. The key of this patch is the new mechanism in bpf_program__load(). Instead of loading BPF program into kernel directly, it calls a 'pre-processor' to generate program instances which would be finally loaded into the kernel based on the original code. To enable the generation of multiple instances, libbpf passes an index to the pre-processor so it know which instance is being loaded. Pre-processor should be called from libbpf's user (perf) using bpf_program__set_prep(). The number of instances and the relationship between indices and the target instance should be clear when calling bpf_program__set_prep(). To retrieve a fd for a specific instance of a program, bpf_program__nth_fd() is introduced. It returns the resulting fd according to index. Signed-off-by: He Kuang Cc: Alexei Starovoitov Cc: He Kuang Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-8-git-send-email-wangnan0@huawei.com Signed-off-by: Wang Nan [ Enclosed multi-line if/else blocks with {}, (*func_ptr)() -> func_ptr() ] Signed-off-by: Arnaldo Carvalho de Melo --- tools/lib/bpf/libbpf.c | 146 ++++++++++++++++++++++++++++++++++++++--- tools/lib/bpf/libbpf.h | 64 ++++++++++++++++++ 2 files changed, 201 insertions(+), 9 deletions(-) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index e176bad19bcb..e3f4c3379f14 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -152,7 +152,11 @@ struct bpf_program { } *reloc_desc; int nr_reloc; - int fd; + struct { + int nr; + int *fds; + } instances; + bpf_program_prep_t preprocessor; struct bpf_object *obj; void *priv; @@ -206,10 +210,25 @@ struct bpf_object { static void bpf_program__unload(struct bpf_program *prog) { + int i; + if (!prog) return; - zclose(prog->fd); + /* + * If the object is opened but the program was never loaded, + * it is possible that prog->instances.nr == -1. + */ + if (prog->instances.nr > 0) { + for (i = 0; i < prog->instances.nr; i++) + zclose(prog->instances.fds[i]); + } else if (prog->instances.nr != -1) { + pr_warning("Internal error: instances.nr is %d\n", + prog->instances.nr); + } + + prog->instances.nr = -1; + zfree(&prog->instances.fds); } static void bpf_program__exit(struct bpf_program *prog) @@ -260,7 +279,8 @@ bpf_program__init(void *data, size_t size, char *name, int idx, memcpy(prog->insns, data, prog->insns_cnt * sizeof(struct bpf_insn)); prog->idx = idx; - prog->fd = -1; + prog->instances.fds = NULL; + prog->instances.nr = -1; return 0; errout: @@ -860,13 +880,73 @@ static int bpf_program__load(struct bpf_program *prog, char *license, u32 kern_version) { - int err, fd; + int err = 0, fd, i; - err = load_program(prog->insns, prog->insns_cnt, - license, kern_version, &fd); - if (!err) - prog->fd = fd; + if (prog->instances.nr < 0 || !prog->instances.fds) { + if (prog->preprocessor) { + pr_warning("Internal error: can't load program '%s'\n", + prog->section_name); + return -LIBBPF_ERRNO__INTERNAL; + } + prog->instances.fds = malloc(sizeof(int)); + if (!prog->instances.fds) { + pr_warning("Not enough memory for BPF fds\n"); + return -ENOMEM; + } + prog->instances.nr = 1; + prog->instances.fds[0] = -1; + } + + if (!prog->preprocessor) { + if (prog->instances.nr != 1) { + pr_warning("Program '%s' is inconsistent: nr(%d) != 1\n", + prog->section_name, prog->instances.nr); + } + err = load_program(prog->insns, prog->insns_cnt, + license, kern_version, &fd); + if (!err) + prog->instances.fds[0] = fd; + goto out; + } + + for (i = 0; i < prog->instances.nr; i++) { + struct bpf_prog_prep_result result; + bpf_program_prep_t preprocessor = prog->preprocessor; + + bzero(&result, sizeof(result)); + err = preprocessor(prog, i, prog->insns, + prog->insns_cnt, &result); + if (err) { + pr_warning("Preprocessing the %dth instance of program '%s' failed\n", + i, prog->section_name); + goto out; + } + + if (!result.new_insn_ptr || !result.new_insn_cnt) { + pr_debug("Skip loading the %dth instance of program '%s'\n", + i, prog->section_name); + prog->instances.fds[i] = -1; + if (result.pfd) + *result.pfd = -1; + continue; + } + + err = load_program(result.new_insn_ptr, + result.new_insn_cnt, + license, kern_version, &fd); + + if (err) { + pr_warning("Loading the %dth instance of program '%s' failed\n", + i, prog->section_name); + goto out; + } + + if (result.pfd) + *result.pfd = fd; + prog->instances.fds[i] = fd; + } +out: if (err) pr_warning("failed to load program '%s'\n", prog->section_name); @@ -1121,5 +1201,53 @@ const char *bpf_program__title(struct bpf_program *prog, bool needs_copy) int bpf_program__fd(struct bpf_program *prog) { - return prog->fd; + return bpf_program__nth_fd(prog, 0); +} + +int bpf_program__set_prep(struct bpf_program *prog, int nr_instances, + bpf_program_prep_t prep) +{ + int *instances_fds; + + if (nr_instances <= 0 || !prep) + return -EINVAL; + + if (prog->instances.nr > 0 || prog->instances.fds) { + pr_warning("Can't set pre-processor after loading\n"); + return -EINVAL; + } + + instances_fds = malloc(sizeof(int) * nr_instances); + if (!instances_fds) { + pr_warning("alloc memory failed for fds\n"); + return -ENOMEM; + } + + /* fill all fd with -1 */ + memset(instances_fds, -1, sizeof(int) * nr_instances); + + prog->instances.nr = nr_instances; + prog->instances.fds = instances_fds; + prog->preprocessor = prep; + return 0; +} + +int bpf_program__nth_fd(struct bpf_program *prog, int n) +{ + int fd; + + if (n >= prog->instances.nr || n < 0) { + pr_warning("Can't get the %dth fd from program %s: only %d instances\n", + n, prog->section_name, prog->instances.nr); + return -EINVAL; + } + + fd = prog->instances.fds[n]; + if (fd < 0) { + pr_warning("%dth instance of program '%s' is invalid\n", + n, prog->section_name); + return -ENOENT; + } + + return fd; } diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index c9a9aef2806c..949df4b346cf 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -88,6 +88,70 @@ const char *bpf_program__title(struct bpf_program *prog, bool needs_copy); int bpf_program__fd(struct bpf_program *prog); +struct bpf_insn; + +/* + * Libbpf allows callers to adjust BPF programs before being loaded + * into kernel. One program in an object file can be transform into + * multiple variants to be attached to different code. + * + * bpf_program_prep_t, bpf_program__set_prep and bpf_program__nth_fd + * are APIs for this propose. + * + * - bpf_program_prep_t: + * It defines 'preprocessor', which is a caller defined function + * passed to libbpf through bpf_program__set_prep(), and will be + * called before program is loaded. The processor should adjust + * the program one time for each instances according to the number + * passed to it. + * + * - bpf_program__set_prep: + * Attachs a preprocessor to a BPF program. The number of instances + * whould be created is also passed through this function. + * + * - bpf_program__nth_fd: + * After the program is loaded, get resuling fds from bpf program for + * each instances. + * + * If bpf_program__set_prep() is not used, the program whould be loaded + * without adjustment during bpf_object__load(). The program has only + * one instance. In this case bpf_program__fd(prog) is equal to + * bpf_program__nth_fd(prog, 0). + */ + +struct bpf_prog_prep_result { + /* + * If not NULL, load new instruction array. + * If set to NULL, don't load this instance. + */ + struct bpf_insn *new_insn_ptr; + int new_insn_cnt; + + /* If not NULL, result fd is set to it */ + int *pfd; +}; + +/* + * Parameters of bpf_program_prep_t: + * - prog: The bpf_program being loaded. + * - n: Index of instance being generated. + * - insns: BPF instructions array. + * - insns_cnt:Number of instructions in insns. + * - res: Output parameter, result of transformation. + * + * Return value: + * - Zero: pre-processing success. + * - Non-zero: pre-processing, stop loading. + */ +typedef int (*bpf_program_prep_t)(struct bpf_program *prog, int n, + struct bpf_insn *insns, int insns_cnt, + struct bpf_prog_prep_result *res); + +int bpf_program__set_prep(struct bpf_program *prog, int nr_instance, + bpf_program_prep_t prep); + +int bpf_program__nth_fd(struct bpf_program *prog, int n); + /* * We don't need __attribute__((packed)) now since it is * unnecessary for 'bpf_map_def' because they are all aligned. From 1c0ed63239012aa881cc811f726b549dca7279e4 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 12:10:10 +0000 Subject: [PATCH 06/37] perf bpf: Add BPF_PROLOGUE config options for further patches If both LIBBPF and DWARF are detected, it is possible to create prologue for eBPF programs to help them access kernel data. HAVE_BPF_PROLOGUE and CONFIG_BPF_PROLOGUE are added as flags for this feature. PERF_HAVE_ARCH_REGS_QUERY_REGISTER_OFFSET is introduced in commit 63ab024a5b6f295ca17a293ad81b7c728f49a89a ("perf tools: regs_query_register_offset() infrastructure"), which indicates that an architecture supports converting name of a register to its offset in 'struct pt_regs'. Without this support, BPF_PROLOGUE should be turned off. Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-9-git-send-email-wangnan0@huawei.com Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/config/Makefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/perf/config/Makefile b/tools/perf/config/Makefile index de89ec574361..6eb9a956a408 100644 --- a/tools/perf/config/Makefile +++ b/tools/perf/config/Makefile @@ -318,6 +318,18 @@ ifndef NO_LIBELF CFLAGS += -DHAVE_LIBBPF_SUPPORT $(call detected,CONFIG_LIBBPF) endif + + ifndef NO_DWARF + ifdef PERF_HAVE_ARCH_REGS_QUERY_REGISTER_OFFSET + CFLAGS += -DHAVE_BPF_PROLOGUE + $(call detected,CONFIG_BPF_PROLOGUE) + else + msg := $(warning BPF prologue is not supported by architecture $(ARCH), missing regs_query_register_offset()); + endif + else + msg := $(warning DWARF support is off, BPF prologue is disabled); + endif + endif # NO_LIBBPF endif # NO_LIBELF From 30433a3a52b951faab95944e0f8b9d33a1e322ce Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 12:10:11 +0000 Subject: [PATCH 07/37] perf bpf: Compile dwarf-regs.c if CONFIG_BPF_PROLOGUE is on regs_query_register_offset() in dwarf-regs.c is required by BPF prologue. This patch compiles it if CONFIG_BPF_PROLOGUE is on to avoid build failure when CONFIG_BPF_PROLOGUE is on but CONFIG_DWARF is not set. Signed-off-by: He Kuang Acked-by: Masami Hiramatsu Cc: Alexei Starovoitov Cc: He Kuang Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-10-git-send-email-wangnan0@huawei.com Signed-off-by: Wang Nan Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/arch/x86/util/Build | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/perf/arch/x86/util/Build b/tools/perf/arch/x86/util/Build index ff63649fa9ac..465970370f3e 100644 --- a/tools/perf/arch/x86/util/Build +++ b/tools/perf/arch/x86/util/Build @@ -5,6 +5,7 @@ libperf-y += kvm-stat.o libperf-y += perf_regs.o libperf-$(CONFIG_DWARF) += dwarf-regs.o +libperf-$(CONFIG_BPF_PROLOGUE) += dwarf-regs.o libperf-$(CONFIG_LIBUNWIND) += unwind-libunwind.o libperf-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o From 361f2b1d1d7231b8685d990b886f599378a4d5a5 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 12:10:05 +0000 Subject: [PATCH 08/37] perf bpf: Allow BPF program attach to uprobe events This patch adds a new syntax to the BPF object section name to support probing at uprobe event. Now we can use BPF program like this: SEC( "exec=/lib64/libc.so.6;" "libcwrite=__write" ) int libcwrite(void *ctx) { return 1; } Where, in section name of a program, before the main config string, we can use 'key=value' style options. Now the only option key is "exec", for uprobes. Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-4-git-send-email-wangnan0@huawei.com [ Changed the separator from \n to ; ] Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/bpf-loader.c | 120 +++++++++++++++++++++++++++++++++-- tools/perf/util/bpf-loader.h | 1 + 2 files changed, 115 insertions(+), 6 deletions(-) diff --git a/tools/perf/util/bpf-loader.c b/tools/perf/util/bpf-loader.c index 4c50411371db..84169d6f2585 100644 --- a/tools/perf/util/bpf-loader.c +++ b/tools/perf/util/bpf-loader.c @@ -109,6 +109,113 @@ bpf_prog_priv__clear(struct bpf_program *prog __maybe_unused, free(priv); } +static int +config__exec(const char *value, struct perf_probe_event *pev) +{ + pev->uprobes = true; + pev->target = strdup(value); + if (!pev->target) + return -ENOMEM; + return 0; +} + +static struct { + const char *key; + const char *usage; + const char *desc; + int (*func)(const char *, struct perf_probe_event *); +} bpf_config_terms[] = { + { + .key = "exec", + .usage = "exec=", + .desc = "Set uprobe target", + .func = config__exec, + }, +}; + +static int +do_config(const char *key, const char *value, + struct perf_probe_event *pev) +{ + unsigned int i; + + pr_debug("config bpf program: %s=%s\n", key, value); + for (i = 0; i < ARRAY_SIZE(bpf_config_terms); i++) + if (strcmp(key, bpf_config_terms[i].key) == 0) + return bpf_config_terms[i].func(value, pev); + + pr_debug("BPF: ERROR: invalid config option in object: %s=%s\n", + key, value); + + pr_debug("\nHint: Currently valid options are:\n"); + for (i = 0; i < ARRAY_SIZE(bpf_config_terms); i++) + pr_debug("\t%s:\t%s\n", bpf_config_terms[i].usage, + bpf_config_terms[i].desc); + pr_debug("\n"); + + return -BPF_LOADER_ERRNO__CONFIG_TERM; +} + +static const char * +parse_config_kvpair(const char *config_str, struct perf_probe_event *pev) +{ + char *text = strdup(config_str); + char *sep, *line; + const char *main_str = NULL; + int err = 0; + + if (!text) { + pr_debug("No enough memory: dup config_str failed\n"); + return ERR_PTR(-ENOMEM); + } + + line = text; + while ((sep = strchr(line, ';'))) { + char *equ; + + *sep = '\0'; + equ = strchr(line, '='); + if (!equ) { + pr_warning("WARNING: invalid config in BPF object: %s\n", + line); + pr_warning("\tShould be 'key=value'.\n"); + goto nextline; + } + *equ = '\0'; + + err = do_config(line, equ + 1, pev); + if (err) + break; +nextline: + line = sep + 1; + } + + if (!err) + main_str = config_str + (line - text); + free(text); + + return err ? ERR_PTR(err) : main_str; +} + +static int +parse_config(const char *config_str, struct perf_probe_event *pev) +{ + int err; + const char *main_str = parse_config_kvpair(config_str, pev); + + if (IS_ERR(main_str)) + return PTR_ERR(main_str); + + err = parse_perf_probe_command(main_str, pev); + if (err < 0) { + pr_debug("bpf: '%s' is not a valid config string\n", + config_str); + /* parse failed, don't need clear pev. */ + return -BPF_LOADER_ERRNO__CONFIG; + } + return 0; +} + static int config_bpf_program(struct bpf_program *prog) { @@ -131,13 +238,9 @@ config_bpf_program(struct bpf_program *prog) pev = &priv->pev; pr_debug("bpf: config program '%s'\n", config_str); - err = parse_perf_probe_command(config_str, pev); - if (err < 0) { - pr_debug("bpf: '%s' is not a valid config string\n", - config_str); - err = -BPF_LOADER_ERRNO__CONFIG; + err = parse_config(config_str, pev); + if (err) goto errout; - } if (pev->group && strcmp(pev->group, PERF_BPF_PROBE_GROUP)) { pr_debug("bpf: '%s': group for event is set and not '%s'.\n", @@ -340,6 +443,7 @@ static const char *bpf_loader_strerror_table[NR_ERRNO] = { [ERRCODE_OFFSET(EVENTNAME)] = "No event name found in config string", [ERRCODE_OFFSET(INTERNAL)] = "BPF loader internal error", [ERRCODE_OFFSET(COMPILE)] = "Error when compiling BPF scriptlet", + [ERRCODE_OFFSET(CONFIG_TERM)] = "Invalid config term in config string", }; static int @@ -420,6 +524,10 @@ int bpf__strerror_probe(struct bpf_object *obj __maybe_unused, int err, char *buf, size_t size) { bpf__strerror_head(err, buf, size); + case BPF_LOADER_ERRNO__CONFIG_TERM: { + scnprintf(buf, size, "%s (add -v to see detail)", emsg); + break; + } bpf__strerror_entry(EEXIST, "Probe point exist. Try use 'perf probe -d \"*\"'"); bpf__strerror_entry(EACCES, "You need to be root"); bpf__strerror_entry(EPERM, "You need to be root, and /proc/sys/kernel/kptr_restrict should be 0"); diff --git a/tools/perf/util/bpf-loader.h b/tools/perf/util/bpf-loader.h index 9caf3ae4acf3..d19f5c5d6d74 100644 --- a/tools/perf/util/bpf-loader.h +++ b/tools/perf/util/bpf-loader.h @@ -20,6 +20,7 @@ enum bpf_loader_errno { BPF_LOADER_ERRNO__EVENTNAME, /* Event name is missing */ BPF_LOADER_ERRNO__INTERNAL, /* BPF loader internal error */ BPF_LOADER_ERRNO__COMPILE, /* Error when compiling BPF scriptlet */ + BPF_LOADER_ERRNO__CONFIG_TERM, /* Invalid config term in config term */ __BPF_LOADER_ERRNO__END, }; From 5dbd16c0c9d17ab1ab2226a5926482c26c0287ed Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 12:10:06 +0000 Subject: [PATCH 09/37] perf bpf: Allow attaching BPF programs to modules symbols By extending the syntax of BPF object section names, this patch allows users to attach BPF programs to symbols in modules. For example: SEC("module=i915;" "parse_cmds=i915_parse_cmds") int parse_cmds(void *ctx) { return 1; } The implementation is very simple: like what 'perf probe' does, for module, fill 'uprobe' field in 'struct perf_probe_event'. Other parts will be done automatically. Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Brendan Gregg Cc: Daniel Borkmann Cc: David Ahern Cc: He Kuang Cc: Jiri Olsa Cc: Kaixu Xia Cc: Masami Hiramatsu Cc: Namhyung Kim Cc: Peter Zijlstra Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-5-git-send-email-wangnan0@huawei.com Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/bpf-loader.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/perf/util/bpf-loader.c b/tools/perf/util/bpf-loader.c index 84169d6f2585..d0f02ed93804 100644 --- a/tools/perf/util/bpf-loader.c +++ b/tools/perf/util/bpf-loader.c @@ -119,6 +119,16 @@ config__exec(const char *value, struct perf_probe_event *pev) return 0; } +static int +config__module(const char *value, struct perf_probe_event *pev) +{ + pev->uprobes = false; + pev->target = strdup(value); + if (!pev->target) + return -ENOMEM; + return 0; +} + static struct { const char *key; const char *usage; @@ -131,6 +141,12 @@ static struct { .desc = "Set uprobe target", .func = config__exec, }, + { + .key = "module", + .usage = "module= ", + .desc = "Set kprobe module", + .func = config__module, + } }; static int From 03e01f568759ddbfdaff892e299758e7771a3478 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 12:10:08 +0000 Subject: [PATCH 10/37] perf bpf: Allow BPF program config probing options By extending the syntax of BPF object section names, this patch allows users to config probing options like what they can do in 'perf probe'. The error message in 'perf probe' is also updated. Test result: For following BPF file test_probe_glob.c: # cat test_probe_glob.c __attribute__((section("inlines=no;func=SyS_dup?"), used)) int func(void *ctx) { return 1; } char _license[] __attribute__((section("license"), used)) = "GPL"; int _version __attribute__((section("version"), used)) = 0x40300; # # ./perf record -e ./test_probe_glob.c ls / ... [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.013 MB perf.data ] # ./perf evlist perf_bpf_probe:func_1 perf_bpf_probe:func After changing "inlines=no" to "inlines=yes": # ./perf record -e ./test_probe_glob.c ls / ... [ perf record: Woken up 2 times to write data ] [ perf record: Captured and wrote 0.013 MB perf.data ] # ./perf evlist perf_bpf_probe:func_3 perf_bpf_probe:func_2 perf_bpf_probe:func_1 perf_bpf_probe:func Then test 'force': Use following program: # cat test_probe_force.c __attribute__((section("func=sys_write"), used)) int funca(void *ctx) { return 1; } __attribute__((section("force=yes;func=sys_write"), used)) int funcb(void *ctx) { return 1; } char _license[] __attribute__((section("license"), used)) = "GPL"; int _version __attribute__((section("version"), used)) = 0x40300; # # perf record -e ./test_probe_force.c usleep 1 Error: event "func" already exists. Hint: Remove existing event by 'perf probe -d' or force duplicates by 'perf probe -f' or set 'force=yes' in BPF source. event syntax error: './test_probe_force.c' \___ Probe point exist. Try 'perf probe -d "*"' and set 'force=yes' (add -v to see detail) ... Then replace 'force=no' to 'force=yes': # vim test_probe_force.c # perf record -e ./test_probe_force.c usleep 1 [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.017 MB perf.data ] # perf evlist perf_bpf_probe:func_1 perf_bpf_probe:func # Signed-off-by: Wang Nan Tested-by: Arnaldo Carvalho de Melo Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-7-git-send-email-wangnan0@huawei.com Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/bpf-loader.c | 53 +++++++++++++++++++++++++++++++++-- tools/perf/util/probe-event.c | 7 +++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/tools/perf/util/bpf-loader.c b/tools/perf/util/bpf-loader.c index d0f02ed93804..98f2e5d1a5be 100644 --- a/tools/perf/util/bpf-loader.c +++ b/tools/perf/util/bpf-loader.c @@ -7,6 +7,7 @@ #include #include +#include #include "perf.h" #include "debug.h" #include "bpf-loader.h" @@ -129,6 +130,38 @@ config__module(const char *value, struct perf_probe_event *pev) return 0; } +static int +config__bool(const char *value, + bool *pbool, bool invert) +{ + int err; + bool bool_value; + + if (!pbool) + return -EINVAL; + + err = strtobool(value, &bool_value); + if (err) + return err; + + *pbool = invert ? !bool_value : bool_value; + return 0; +} + +static int +config__inlines(const char *value, + struct perf_probe_event *pev __maybe_unused) +{ + return config__bool(value, &probe_conf.no_inlines, true); +} + +static int +config__force(const char *value, + struct perf_probe_event *pev __maybe_unused) +{ + return config__bool(value, &probe_conf.force_add, false); +} + static struct { const char *key; const char *usage; @@ -146,7 +179,19 @@ static struct { .usage = "module= ", .desc = "Set kprobe module", .func = config__module, - } + }, + { + .key = "inlines", + .usage = "inlines=[yes|no] ", + .desc = "Probe at inline symbol", + .func = config__inlines, + }, + { + .key = "force", + .usage = "force=[yes|no] ", + .desc = "Forcibly add events with existing name", + .func = config__force, + }, }; static int @@ -240,6 +285,10 @@ config_bpf_program(struct bpf_program *prog) const char *config_str; int err; + /* Initialize per-program probing setting */ + probe_conf.no_inlines = false; + probe_conf.force_add = false; + config_str = bpf_program__title(prog, false); if (IS_ERR(config_str)) { pr_debug("bpf: unable to get title for program\n"); @@ -544,7 +593,7 @@ int bpf__strerror_probe(struct bpf_object *obj __maybe_unused, scnprintf(buf, size, "%s (add -v to see detail)", emsg); break; } - bpf__strerror_entry(EEXIST, "Probe point exist. Try use 'perf probe -d \"*\"'"); + bpf__strerror_entry(EEXIST, "Probe point exist. Try 'perf probe -d \"*\"' and set 'force=yes'"); bpf__strerror_entry(EACCES, "You need to be root"); bpf__strerror_entry(EPERM, "You need to be root, and /proc/sys/kernel/kptr_restrict should be 0"); bpf__strerror_entry(ENOENT, "You need to check probing points in BPF file"); diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 03875f9154e7..93996ec4bbe3 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -2326,8 +2326,11 @@ static int get_new_event_name(char *buf, size_t len, const char *base, goto out; if (!allow_suffix) { - pr_warning("Error: event \"%s\" already exists. " - "(Use -f to force duplicates.)\n", buf); + pr_warning("Error: event \"%s\" already exists.\n" + " Hint: Remove existing event by 'perf probe -d'\n" + " or force duplicates by 'perf probe -f'\n" + " or set 'force=yes' in BPF source.\n", + buf); ret = -EEXIST; goto out; } From bfc077b4cf106793b30bf942e426ee99f1f4ac44 Mon Sep 17 00:00:00 2001 From: He Kuang Date: Mon, 16 Nov 2015 12:10:12 +0000 Subject: [PATCH 11/37] perf bpf: Add prologue for BPF programs for fetching arguments This patch generates a prologue for a BPF program which fetches arguments for it. With this patch, the program can have arguments as follow: SEC("lock_page=__lock_page page->flags") int lock_page(struct pt_regs *ctx, int err, unsigned long flags) { return 1; } This patch passes at most 3 arguments from r3, r4 and r5. r1 is still the ctx pointer. r2 is used to indicate if dereferencing was done successfully. This patch uses r6 to hold ctx (struct pt_regs) and r7 to hold stack pointer for result. Result of each arguments first store on stack: low address BPF_REG_FP - 24 ARG3 BPF_REG_FP - 16 ARG2 BPF_REG_FP - 8 ARG1 BPF_REG_FP high address Then loaded into r3, r4 and r5. The output prologue for offn(...off2(off1(reg)))) should be: r6 <- r1 // save ctx into a callee saved register r7 <- fp r7 <- r7 - stack_offset // pointer to result slot /* load r3 with the offset in pt_regs of 'reg' */ (r7) <- r3 // make slot valid r3 <- r3 + off1 // prepare to read unsafe pointer r2 <- 8 r1 <- r7 // result put onto stack call probe_read // read unsafe pointer jnei r0, 0, err // error checking r3 <- (r7) // read result r3 <- r3 + off2 // prepare to read unsafe pointer r2 <- 8 r1 <- r7 call probe_read jnei r0, 0, err ... /* load r2, r3, r4 from stack */ goto success err: r2 <- 1 /* load r3, r4, r5 with 0 */ goto usercode success: r2 <- 0 usercode: r1 <- r6 // restore ctx // original user code If all of arguments reside in register (dereferencing is not required), gen_prologue_fastpath() will be used to create fast prologue: r3 <- (r1 + offset of reg1) r4 <- (r1 + offset of reg2) r5 <- (r1 + offset of reg3) r2 <- 0 P.S. eBPF calling convention is defined as: * r0 - return value from in-kernel function, and exit value for eBPF program * r1 - r5 - arguments from eBPF program to in-kernel function * r6 - r9 - callee saved registers that in-kernel function will preserve * r10 - read-only frame pointer to access stack Committer note: At least testing if it builds and loads: # cat test_probe_arg.c struct pt_regs; __attribute__((section("lock_page=__lock_page page->flags"), used)) int func(struct pt_regs *ctx, int err, unsigned long flags) { return 1; } char _license[] __attribute__((section("license"), used)) = "GPL"; int _version __attribute__((section("version"), used)) = 0x40300; # perf record -e ./test_probe_arg.c usleep 1 [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.016 MB perf.data ] # perf evlist perf_bpf_probe:lock_page # Signed-off-by: He Kuang Tested-by: Arnaldo Carvalho de Melo Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Wang Nan Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-11-git-send-email-wangnan0@huawei.com Signed-off-by: Wang Nan Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/Build | 1 + tools/perf/util/bpf-loader.c | 3 + tools/perf/util/bpf-loader.h | 3 + tools/perf/util/bpf-prologue.c | 455 +++++++++++++++++++++++++++++++++ tools/perf/util/bpf-prologue.h | 34 +++ 5 files changed, 496 insertions(+) create mode 100644 tools/perf/util/bpf-prologue.c create mode 100644 tools/perf/util/bpf-prologue.h diff --git a/tools/perf/util/Build b/tools/perf/util/Build index e2316900f96f..0513dd525d87 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -89,6 +89,7 @@ libperf-y += parse-branch-options.o libperf-y += parse-regs-options.o libperf-$(CONFIG_LIBBPF) += bpf-loader.o +libperf-$(CONFIG_BPF_PROLOGUE) += bpf-prologue.o libperf-$(CONFIG_LIBELF) += symbol-elf.o libperf-$(CONFIG_LIBELF) += probe-file.o libperf-$(CONFIG_LIBELF) += probe-event.o diff --git a/tools/perf/util/bpf-loader.c b/tools/perf/util/bpf-loader.c index 98f2e5d1a5be..bd14be438cda 100644 --- a/tools/perf/util/bpf-loader.c +++ b/tools/perf/util/bpf-loader.c @@ -509,6 +509,9 @@ static const char *bpf_loader_strerror_table[NR_ERRNO] = { [ERRCODE_OFFSET(INTERNAL)] = "BPF loader internal error", [ERRCODE_OFFSET(COMPILE)] = "Error when compiling BPF scriptlet", [ERRCODE_OFFSET(CONFIG_TERM)] = "Invalid config term in config string", + [ERRCODE_OFFSET(PROLOGUE)] = "Failed to generate prologue", + [ERRCODE_OFFSET(PROLOGUE2BIG)] = "Prologue too big for program", + [ERRCODE_OFFSET(PROLOGUEOOB)] = "Offset out of bound for prologue", }; static int diff --git a/tools/perf/util/bpf-loader.h b/tools/perf/util/bpf-loader.h index d19f5c5d6d74..a58740b0f31e 100644 --- a/tools/perf/util/bpf-loader.h +++ b/tools/perf/util/bpf-loader.h @@ -21,6 +21,9 @@ enum bpf_loader_errno { BPF_LOADER_ERRNO__INTERNAL, /* BPF loader internal error */ BPF_LOADER_ERRNO__COMPILE, /* Error when compiling BPF scriptlet */ BPF_LOADER_ERRNO__CONFIG_TERM, /* Invalid config term in config term */ + BPF_LOADER_ERRNO__PROLOGUE, /* Failed to generate prologue */ + BPF_LOADER_ERRNO__PROLOGUE2BIG, /* Prologue too big for program */ + BPF_LOADER_ERRNO__PROLOGUEOOB, /* Offset out of bound for prologue */ __BPF_LOADER_ERRNO__END, }; diff --git a/tools/perf/util/bpf-prologue.c b/tools/perf/util/bpf-prologue.c new file mode 100644 index 000000000000..6cdbee119ceb --- /dev/null +++ b/tools/perf/util/bpf-prologue.c @@ -0,0 +1,455 @@ +/* + * bpf-prologue.c + * + * Copyright (C) 2015 He Kuang + * Copyright (C) 2015 Wang Nan + * Copyright (C) 2015 Huawei Inc. + */ + +#include +#include "perf.h" +#include "debug.h" +#include "bpf-loader.h" +#include "bpf-prologue.h" +#include "probe-finder.h" +#include +#include + +#define BPF_REG_SIZE 8 + +#define JMP_TO_ERROR_CODE -1 +#define JMP_TO_SUCCESS_CODE -2 +#define JMP_TO_USER_CODE -3 + +struct bpf_insn_pos { + struct bpf_insn *begin; + struct bpf_insn *end; + struct bpf_insn *pos; +}; + +static inline int +pos_get_cnt(struct bpf_insn_pos *pos) +{ + return pos->pos - pos->begin; +} + +static int +append_insn(struct bpf_insn new_insn, struct bpf_insn_pos *pos) +{ + if (!pos->pos) + return -BPF_LOADER_ERRNO__PROLOGUE2BIG; + + if (pos->pos + 1 >= pos->end) { + pr_err("bpf prologue: prologue too long\n"); + pos->pos = NULL; + return -BPF_LOADER_ERRNO__PROLOGUE2BIG; + } + + *(pos->pos)++ = new_insn; + return 0; +} + +static int +check_pos(struct bpf_insn_pos *pos) +{ + if (!pos->pos || pos->pos >= pos->end) + return -BPF_LOADER_ERRNO__PROLOGUE2BIG; + return 0; +} + +/* Give it a shorter name */ +#define ins(i, p) append_insn((i), (p)) + +/* + * Give a register name (in 'reg'), generate instruction to + * load register into an eBPF register rd: + * 'ldd target_reg, offset(ctx_reg)', where: + * ctx_reg is pre initialized to pointer of 'struct pt_regs'. + */ +static int +gen_ldx_reg_from_ctx(struct bpf_insn_pos *pos, int ctx_reg, + const char *reg, int target_reg) +{ + int offset = regs_query_register_offset(reg); + + if (offset < 0) { + pr_err("bpf: prologue: failed to get register %s\n", + reg); + return offset; + } + ins(BPF_LDX_MEM(BPF_DW, target_reg, ctx_reg, offset), pos); + + return check_pos(pos); +} + +/* + * Generate a BPF_FUNC_probe_read function call. + * + * src_base_addr_reg is a register holding base address, + * dst_addr_reg is a register holding dest address (on stack), + * result is: + * + * *[dst_addr_reg] = *([src_base_addr_reg] + offset) + * + * Arguments of BPF_FUNC_probe_read: + * ARG1: ptr to stack (dest) + * ARG2: size (8) + * ARG3: unsafe ptr (src) + */ +static int +gen_read_mem(struct bpf_insn_pos *pos, + int src_base_addr_reg, + int dst_addr_reg, + long offset) +{ + /* mov arg3, src_base_addr_reg */ + if (src_base_addr_reg != BPF_REG_ARG3) + ins(BPF_MOV64_REG(BPF_REG_ARG3, src_base_addr_reg), pos); + /* add arg3, #offset */ + if (offset) + ins(BPF_ALU64_IMM(BPF_ADD, BPF_REG_ARG3, offset), pos); + + /* mov arg2, #reg_size */ + ins(BPF_ALU64_IMM(BPF_MOV, BPF_REG_ARG2, BPF_REG_SIZE), pos); + + /* mov arg1, dst_addr_reg */ + if (dst_addr_reg != BPF_REG_ARG1) + ins(BPF_MOV64_REG(BPF_REG_ARG1, dst_addr_reg), pos); + + /* Call probe_read */ + ins(BPF_EMIT_CALL(BPF_FUNC_probe_read), pos); + /* + * Error processing: if read fail, goto error code, + * will be relocated. Target should be the start of + * error processing code. + */ + ins(BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, JMP_TO_ERROR_CODE), + pos); + + return check_pos(pos); +} + +/* + * Each arg should be bare register. Fetch and save them into argument + * registers (r3 - r5). + * + * BPF_REG_1 should have been initialized with pointer to + * 'struct pt_regs'. + */ +static int +gen_prologue_fastpath(struct bpf_insn_pos *pos, + struct probe_trace_arg *args, int nargs) +{ + int i, err = 0; + + for (i = 0; i < nargs; i++) { + err = gen_ldx_reg_from_ctx(pos, BPF_REG_1, args[i].value, + BPF_PROLOGUE_START_ARG_REG + i); + if (err) + goto errout; + } + + return check_pos(pos); +errout: + return err; +} + +/* + * Slow path: + * At least one argument has the form of 'offset($rx)'. + * + * Following code first stores them into stack, then loads all of then + * to r2 - r5. + * Before final loading, the final result should be: + * + * low address + * BPF_REG_FP - 24 ARG3 + * BPF_REG_FP - 16 ARG2 + * BPF_REG_FP - 8 ARG1 + * BPF_REG_FP + * high address + * + * For each argument (described as: offn(...off2(off1(reg)))), + * generates following code: + * + * r7 <- fp + * r7 <- r7 - stack_offset // Ideal code should initialize r7 using + * // fp before generating args. However, + * // eBPF won't regard r7 as stack pointer + * // if it is generated by minus 8 from + * // another stack pointer except fp. + * // This is why we have to set r7 + * // to fp for each variable. + * r3 <- value of 'reg'-> generated using gen_ldx_reg_from_ctx() + * (r7) <- r3 // skip following instructions for bare reg + * r3 <- r3 + off1 . // skip if off1 == 0 + * r2 <- 8 \ + * r1 <- r7 |-> generated by gen_read_mem() + * call probe_read / + * jnei r0, 0, err ./ + * r3 <- (r7) + * r3 <- r3 + off2 . // skip if off2 == 0 + * r2 <- 8 \ // r2 may be broken by probe_read, so set again + * r1 <- r7 |-> generated by gen_read_mem() + * call probe_read / + * jnei r0, 0, err ./ + * ... + */ +static int +gen_prologue_slowpath(struct bpf_insn_pos *pos, + struct probe_trace_arg *args, int nargs) +{ + int err, i; + + for (i = 0; i < nargs; i++) { + struct probe_trace_arg *arg = &args[i]; + const char *reg = arg->value; + struct probe_trace_arg_ref *ref = NULL; + int stack_offset = (i + 1) * -8; + + pr_debug("prologue: fetch arg %d, base reg is %s\n", + i, reg); + + /* value of base register is stored into ARG3 */ + err = gen_ldx_reg_from_ctx(pos, BPF_REG_CTX, reg, + BPF_REG_ARG3); + if (err) { + pr_err("prologue: failed to get offset of register %s\n", + reg); + goto errout; + } + + /* Make r7 the stack pointer. */ + ins(BPF_MOV64_REG(BPF_REG_7, BPF_REG_FP), pos); + /* r7 += -8 */ + ins(BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, stack_offset), pos); + /* + * Store r3 (base register) onto stack + * Ensure fp[offset] is set. + * fp is the only valid base register when storing + * into stack. We are not allowed to use r7 as base + * register here. + */ + ins(BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_ARG3, + stack_offset), pos); + + ref = arg->ref; + while (ref) { + pr_debug("prologue: arg %d: offset %ld\n", + i, ref->offset); + err = gen_read_mem(pos, BPF_REG_3, BPF_REG_7, + ref->offset); + if (err) { + pr_err("prologue: failed to generate probe_read function call\n"); + goto errout; + } + + ref = ref->next; + /* + * Load previous result into ARG3. Use + * BPF_REG_FP instead of r7 because verifier + * allows FP based addressing only. + */ + if (ref) + ins(BPF_LDX_MEM(BPF_DW, BPF_REG_ARG3, + BPF_REG_FP, stack_offset), pos); + } + } + + /* Final pass: read to registers */ + for (i = 0; i < nargs; i++) + ins(BPF_LDX_MEM(BPF_DW, BPF_PROLOGUE_START_ARG_REG + i, + BPF_REG_FP, -BPF_REG_SIZE * (i + 1)), pos); + + ins(BPF_JMP_IMM(BPF_JA, BPF_REG_0, 0, JMP_TO_SUCCESS_CODE), pos); + + return check_pos(pos); +errout: + return err; +} + +static int +prologue_relocate(struct bpf_insn_pos *pos, struct bpf_insn *error_code, + struct bpf_insn *success_code, struct bpf_insn *user_code) +{ + struct bpf_insn *insn; + + if (check_pos(pos)) + return -BPF_LOADER_ERRNO__PROLOGUE2BIG; + + for (insn = pos->begin; insn < pos->pos; insn++) { + struct bpf_insn *target; + u8 class = BPF_CLASS(insn->code); + u8 opcode; + + if (class != BPF_JMP) + continue; + opcode = BPF_OP(insn->code); + if (opcode == BPF_CALL) + continue; + + switch (insn->off) { + case JMP_TO_ERROR_CODE: + target = error_code; + break; + case JMP_TO_SUCCESS_CODE: + target = success_code; + break; + case JMP_TO_USER_CODE: + target = user_code; + break; + default: + pr_err("bpf prologue: internal error: relocation failed\n"); + return -BPF_LOADER_ERRNO__PROLOGUE; + } + + insn->off = target - (insn + 1); + } + return 0; +} + +int bpf__gen_prologue(struct probe_trace_arg *args, int nargs, + struct bpf_insn *new_prog, size_t *new_cnt, + size_t cnt_space) +{ + struct bpf_insn *success_code = NULL; + struct bpf_insn *error_code = NULL; + struct bpf_insn *user_code = NULL; + struct bpf_insn_pos pos; + bool fastpath = true; + int err = 0, i; + + if (!new_prog || !new_cnt) + return -EINVAL; + + if (cnt_space > BPF_MAXINSNS) + cnt_space = BPF_MAXINSNS; + + pos.begin = new_prog; + pos.end = new_prog + cnt_space; + pos.pos = new_prog; + + if (!nargs) { + ins(BPF_ALU64_IMM(BPF_MOV, BPF_PROLOGUE_FETCH_RESULT_REG, 0), + &pos); + + if (check_pos(&pos)) + goto errout; + + *new_cnt = pos_get_cnt(&pos); + return 0; + } + + if (nargs > BPF_PROLOGUE_MAX_ARGS) { + pr_warning("bpf: prologue: %d arguments are dropped\n", + nargs - BPF_PROLOGUE_MAX_ARGS); + nargs = BPF_PROLOGUE_MAX_ARGS; + } + + /* First pass: validation */ + for (i = 0; i < nargs; i++) { + struct probe_trace_arg_ref *ref = args[i].ref; + + if (args[i].value[0] == '@') { + /* TODO: fetch global variable */ + pr_err("bpf: prologue: global %s%+ld not support\n", + args[i].value, ref ? ref->offset : 0); + return -ENOTSUP; + } + + while (ref) { + /* fastpath is true if all args has ref == NULL */ + fastpath = false; + + /* + * Instruction encodes immediate value using + * s32, ref->offset is long. On systems which + * can't fill long in s32, refuse to process if + * ref->offset too large (or small). + */ +#ifdef __LP64__ +#define OFFSET_MAX ((1LL << 31) - 1) +#define OFFSET_MIN ((1LL << 31) * -1) + if (ref->offset > OFFSET_MAX || + ref->offset < OFFSET_MIN) { + pr_err("bpf: prologue: offset out of bound: %ld\n", + ref->offset); + return -BPF_LOADER_ERRNO__PROLOGUEOOB; + } +#endif + ref = ref->next; + } + } + pr_debug("prologue: pass validation\n"); + + if (fastpath) { + /* If all variables are registers... */ + pr_debug("prologue: fast path\n"); + err = gen_prologue_fastpath(&pos, args, nargs); + if (err) + goto errout; + } else { + pr_debug("prologue: slow path\n"); + + /* Initialization: move ctx to a callee saved register. */ + ins(BPF_MOV64_REG(BPF_REG_CTX, BPF_REG_ARG1), &pos); + + err = gen_prologue_slowpath(&pos, args, nargs); + if (err) + goto errout; + /* + * start of ERROR_CODE (only slow pass needs error code) + * mov r2 <- 1 // r2 is error number + * mov r3 <- 0 // r3, r4... should be touched or + * // verifier would complain + * mov r4 <- 0 + * ... + * goto usercode + */ + error_code = pos.pos; + ins(BPF_ALU64_IMM(BPF_MOV, BPF_PROLOGUE_FETCH_RESULT_REG, 1), + &pos); + + for (i = 0; i < nargs; i++) + ins(BPF_ALU64_IMM(BPF_MOV, + BPF_PROLOGUE_START_ARG_REG + i, + 0), + &pos); + ins(BPF_JMP_IMM(BPF_JA, BPF_REG_0, 0, JMP_TO_USER_CODE), + &pos); + } + + /* + * start of SUCCESS_CODE: + * mov r2 <- 0 + * goto usercode // skip + */ + success_code = pos.pos; + ins(BPF_ALU64_IMM(BPF_MOV, BPF_PROLOGUE_FETCH_RESULT_REG, 0), &pos); + + /* + * start of USER_CODE: + * Restore ctx to r1 + */ + user_code = pos.pos; + if (!fastpath) { + /* + * Only slow path needs restoring of ctx. In fast path, + * register are loaded directly from r1. + */ + ins(BPF_MOV64_REG(BPF_REG_ARG1, BPF_REG_CTX), &pos); + err = prologue_relocate(&pos, error_code, success_code, + user_code); + if (err) + goto errout; + } + + err = check_pos(&pos); + if (err) + goto errout; + + *new_cnt = pos_get_cnt(&pos); + return 0; +errout: + return err; +} diff --git a/tools/perf/util/bpf-prologue.h b/tools/perf/util/bpf-prologue.h new file mode 100644 index 000000000000..d94cbea12899 --- /dev/null +++ b/tools/perf/util/bpf-prologue.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015, He Kuang + * Copyright (C) 2015, Huawei Inc. + */ +#ifndef __BPF_PROLOGUE_H +#define __BPF_PROLOGUE_H + +#include +#include +#include "probe-event.h" + +#define BPF_PROLOGUE_MAX_ARGS 3 +#define BPF_PROLOGUE_START_ARG_REG BPF_REG_3 +#define BPF_PROLOGUE_FETCH_RESULT_REG BPF_REG_2 + +#ifdef HAVE_BPF_PROLOGUE +int bpf__gen_prologue(struct probe_trace_arg *args, int nargs, + struct bpf_insn *new_prog, size_t *new_cnt, + size_t cnt_space); +#else +static inline int +bpf__gen_prologue(struct probe_trace_arg *args __maybe_unused, + int nargs __maybe_unused, + struct bpf_insn *new_prog __maybe_unused, + size_t *new_cnt, + size_t cnt_space __maybe_unused) +{ + if (!new_cnt) + return -EINVAL; + *new_cnt = 0; + return -ENOTSUP; +} +#endif +#endif /* __BPF_PROLOGUE_H */ From a08357d8dc7d3025d1094f727ad1f7e837766f93 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 12:10:13 +0000 Subject: [PATCH 12/37] perf bpf: Generate prologue for BPF programs This patch generates a prologue for each 'struct probe_trace_event' for fetching arguments for BPF programs. After bpf__probe(), iterate over each program to check whether prologues are required. If none of the 'struct perf_probe_event' programs will attach to have at least one argument, simply skip preprocessor hooking. For those who a prologue is required, call bpf__gen_prologue() and paste the original instruction after the prologue. Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-12-git-send-email-wangnan0@huawei.com Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/bpf-loader.c | 120 ++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/bpf-loader.c b/tools/perf/util/bpf-loader.c index bd14be438cda..190a1c7f0649 100644 --- a/tools/perf/util/bpf-loader.c +++ b/tools/perf/util/bpf-loader.c @@ -5,12 +5,15 @@ * Copyright (C) 2015 Huawei Inc. */ +#include #include #include #include #include "perf.h" #include "debug.h" #include "bpf-loader.h" +#include "bpf-prologue.h" +#include "llvm-utils.h" #include "probe-event.h" #include "probe-finder.h" // for MAX_PROBES #include "llvm-utils.h" @@ -33,6 +36,8 @@ DEFINE_PRINT_FN(debug, 1) struct bpf_prog_priv { struct perf_probe_event pev; + bool need_prologue; + struct bpf_insn *insns_buf; }; static bool libbpf_initialized; @@ -107,6 +112,7 @@ bpf_prog_priv__clear(struct bpf_program *prog __maybe_unused, struct bpf_prog_priv *priv = _priv; cleanup_perf_probe_events(&priv->pev, 1); + zfree(&priv->insns_buf); free(priv); } @@ -365,6 +371,102 @@ static int bpf__prepare_probe(void) return err; } +static int +preproc_gen_prologue(struct bpf_program *prog, int n, + struct bpf_insn *orig_insns, int orig_insns_cnt, + struct bpf_prog_prep_result *res) +{ + struct probe_trace_event *tev; + struct perf_probe_event *pev; + struct bpf_prog_priv *priv; + struct bpf_insn *buf; + size_t prologue_cnt = 0; + int err; + + err = bpf_program__get_private(prog, (void **)&priv); + if (err || !priv) + goto errout; + + pev = &priv->pev; + + if (n < 0 || n >= pev->ntevs) + goto errout; + + tev = &pev->tevs[n]; + + buf = priv->insns_buf; + err = bpf__gen_prologue(tev->args, tev->nargs, + buf, &prologue_cnt, + BPF_MAXINSNS - orig_insns_cnt); + if (err) { + const char *title; + + title = bpf_program__title(prog, false); + if (!title) + title = "[unknown]"; + + pr_debug("Failed to generate prologue for program %s\n", + title); + return err; + } + + memcpy(&buf[prologue_cnt], orig_insns, + sizeof(struct bpf_insn) * orig_insns_cnt); + + res->new_insn_ptr = buf; + res->new_insn_cnt = prologue_cnt + orig_insns_cnt; + res->pfd = NULL; + return 0; + +errout: + pr_debug("Internal error in preproc_gen_prologue\n"); + return -BPF_LOADER_ERRNO__PROLOGUE; +} + +static int hook_load_preprocessor(struct bpf_program *prog) +{ + struct perf_probe_event *pev; + struct bpf_prog_priv *priv; + bool need_prologue = false; + int err, i; + + err = bpf_program__get_private(prog, (void **)&priv); + if (err || !priv) { + pr_debug("Internal error when hook preprocessor\n"); + return -BPF_LOADER_ERRNO__INTERNAL; + } + + pev = &priv->pev; + for (i = 0; i < pev->ntevs; i++) { + struct probe_trace_event *tev = &pev->tevs[i]; + + if (tev->nargs > 0) { + need_prologue = true; + break; + } + } + + /* + * Since all tevs don't have argument, we don't need generate + * prologue. + */ + if (!need_prologue) { + priv->need_prologue = false; + return 0; + } + + priv->need_prologue = true; + priv->insns_buf = malloc(sizeof(struct bpf_insn) * BPF_MAXINSNS); + if (!priv->insns_buf) { + pr_debug("No enough memory: alloc insns_buf failed\n"); + return -ENOMEM; + } + + err = bpf_program__set_prep(prog, pev->ntevs, + preproc_gen_prologue); + return err; +} + int bpf__probe(struct bpf_object *obj) { int err = 0; @@ -399,6 +501,18 @@ int bpf__probe(struct bpf_object *obj) pr_debug("bpf_probe: failed to apply perf probe events"); goto out; } + + /* + * After probing, let's consider prologue, which + * adds program fetcher to BPF programs. + * + * hook_load_preprocessorr() hooks pre-processor + * to bpf_program, let it generate prologue + * dynamically during loading. + */ + err = hook_load_preprocessor(prog); + if (err) + goto out; } out: return err < 0 ? err : 0; @@ -482,7 +596,11 @@ int bpf__foreach_tev(struct bpf_object *obj, for (i = 0; i < pev->ntevs; i++) { tev = &pev->tevs[i]; - fd = bpf_program__fd(prog); + if (priv->need_prologue) + fd = bpf_program__nth_fd(prog, i); + else + fd = bpf_program__fd(prog); + if (fd < 0) { pr_debug("bpf: failed to get file descriptor\n"); return fd; From bbb7d4925a05ecd5bbfdbc1147d402b0db203a5a Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Mon, 16 Nov 2015 12:10:14 +0000 Subject: [PATCH 13/37] perf test: Test the BPF prologue adding infrastructure This patch introduces a new BPF script to test the BPF prologue adding routines. The new script probes at null_lseek, which is the function pointer used when we try to lseek on '/dev/null'. The null_lseek function is chosen because it is used by function pointers, so we don't need to consider inlining and LTO. By extracting file->f_mode, bpf-script-test-prologue.c should know whether the file is writable or readonly. According to llseek_loop() and bpf-script-test-prologue.c, one fourth of total lseeks should be collected. Committer note: Testing it: # perf test -v BPF Kernel build dir is set to /lib/modules/4.3.0+/build set env: KBUILD_DIR=/lib/modules/4.3.0+/build unset env: KBUILD_OPTS include option is set to -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/4.9.2/include -I/home/git/linux/arch/x86/include -Iarch/x86/include/generated/uapi -Iarch/x86/include/generated -I/home/git/linux/include -Iinclude -I/home/git/linux/arch/x86/include/uapi -Iarch/x86/include/generated/uapi -I/home/git/linux/include/uapi -Iinclude/generated/uapi -include /home/git/linux/include/linux/kconfig.h set env: NR_CPUS=4 set env: LINUX_VERSION_CODE=0x40300 set env: CLANG_EXEC=/usr/libexec/icecc/bin/clang set env: CLANG_OPTIONS=-xc set env: KERNEL_INC_OPTIONS= -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/4.9.2/include -I/home/git/linux/arch/x86/include -Iarch/x86/include/generated/uapi -Iarch/x86/include/generated -I/home/git/linux/include -Iinclude -I/home/git/linux/arch/x86/include/uapi -Iarch/x86/include/generated/uapi -I/home/git/linux/include/uapi -Iinclude/generated/uapi -include /home/git/linux/include/linux/kconfig.h set env: WORKING_DIR=/lib/modules/4.3.0+/build set env: CLANG_SOURCE=- llvm compiling command template: echo '/* * bpf-script-test-prologue.c * Test BPF prologue */ #ifndef LINUX_VERSION_CODE # error Need LINUX_VERSION_CODE # error Example: for 4.2 kernel, put 'clang-opt="-DLINUX_VERSION_CODE=0x40200" into llvm section of ~/.perfconfig' #endif #define SEC(NAME) __attribute__((section(NAME), used)) #include #define FMODE_READ 0x1 #define FMODE_WRITE 0x2 static void (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) = (void *) 6; SEC("func=null_lseek file->f_mode offset orig") int bpf_func__null_lseek(void *ctx, int err, unsigned long f_mode, unsigned long offset, unsigned long orig) { if (err) return 0; if (f_mode & FMODE_WRITE) return 0; if (offset & 1) return 0; if (orig == SEEK_CUR) return 0; return 1; } char _license[] SEC("license") = "GPL"; int _version SEC("version") = LINUX_VERSION_CODE; ' | $CLANG_EXEC -D__KERNEL__ -D__NR_CPUS__=$NR_CPUS -DLINUX_VERSION_CODE=$LINUX_VERSION_CODE $CLANG_OPTIONS $KERNEL_INC_OPTIONS -Wno-unused-value -Wno-pointer-sign -working-directory $WORKING_DIR -c "$CLANG_SOURCE" -target bpf -O2 -o - libbpf: loading object '[bpf_prologue_test]' from buffer libbpf: section .strtab, size 135, link 0, flags 0, type=3 libbpf: section .text, size 0, link 0, flags 6, type=1 libbpf: section .data, size 0, link 0, flags 3, type=1 libbpf: section .bss, size 0, link 0, flags 3, type=8 libbpf: section func=null_lseek file->f_mode offset orig, size 112, link 0, flags 6, type=1 libbpf: found program func=null_lseek file->f_mode offset orig libbpf: section license, size 4, link 0, flags 3, type=1 libbpf: license of [bpf_prologue_test] is GPL libbpf: section version, size 4, link 0, flags 3, type=1 libbpf: kernel version of [bpf_prologue_test] is 40300 libbpf: section .symtab, size 168, link 1, flags 0, type=2 bpf: config program 'func=null_lseek file->f_mode offset orig' symbol:null_lseek file:(null) line:0 offset:0 return:0 lazy:(null) parsing arg: file->f_mode into file, f_mode(1) parsing arg: offset into offset parsing arg: orig into orig bpf: config 'func=null_lseek file->f_mode offset orig' is ok Looking at the vmlinux_path (7 entries long) Using /lib/modules/4.3.0+/build/vmlinux for symbols Open Debuginfo file: /lib/modules/4.3.0+/build/vmlinux Try to find probe point from debuginfo. Matched function: null_lseek Probe point found: null_lseek+0 Searching 'file' variable in context. Converting variable file into trace event. converting f_mode in file f_mode type is unsigned int. Searching 'offset' variable in context. Converting variable offset into trace event. offset type is long long int. Searching 'orig' variable in context. Converting variable orig into trace event. orig type is int. Found 1 probe_trace_events. Opening /sys/kernel/debug/tracing//kprobe_events write=1 Writing event: p:perf_bpf_probe/func _text+4840528 f_mode=+68(%di):u32 offset=%si:s64 orig=%dx:s32 libbpf: don't need create maps for [bpf_prologue_test] prologue: pass validation prologue: slow path prologue: fetch arg 0, base reg is %di prologue: arg 0: offset 68 prologue: fetch arg 1, base reg is %si prologue: fetch arg 2, base reg is %dx add bpf event perf_bpf_probe:func and attach bpf program 3 adding perf_bpf_probe:func adding perf_bpf_probe:func to 0x51672c0 mmap size 1052672B Opening /sys/kernel/debug/tracing//kprobe_events write=1 Opening /sys/kernel/debug/tracing//uprobe_events write=1 Parsing probe_events: p:perf_bpf_probe/func _text+4840528 f_mode=+68(%di):u32 offset=%si:s64 orig=%dx:s32 Group:perf_bpf_probe Event:func probe:p Writing event: -:perf_bpf_probe/func test child finished with 0 ---- end ---- Test BPF filter: Ok # Signed-off-by: Wang Nan Tested-by: Arnaldo Carvalho de Melo Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447675815-166222-13-git-send-email-wangnan0@huawei.com [ Added tools/perf/tests/llvm-src-prologue.c to .gitignore ] Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/.gitignore | 1 + tools/perf/tests/Build | 9 +++++- tools/perf/tests/bpf-script-test-prologue.c | 35 +++++++++++++++++++++ tools/perf/tests/bpf.c | 34 ++++++++++++++++++++ tools/perf/tests/llvm.c | 4 +++ tools/perf/tests/llvm.h | 2 ++ 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tools/perf/tests/bpf-script-test-prologue.c diff --git a/tools/perf/tests/.gitignore b/tools/perf/tests/.gitignore index 489fc9ffbcb0..bf016c439fbd 100644 --- a/tools/perf/tests/.gitignore +++ b/tools/perf/tests/.gitignore @@ -1,2 +1,3 @@ llvm-src-base.c llvm-src-kbuild.c +llvm-src-prologue.c diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build index f41ebf8849fe..0ff8a973b81c 100644 --- a/tools/perf/tests/Build +++ b/tools/perf/tests/Build @@ -31,7 +31,7 @@ perf-y += sample-parsing.o perf-y += parse-no-sample-id-all.o perf-y += kmod-path.o perf-y += thread-map.o -perf-y += llvm.o llvm-src-base.o llvm-src-kbuild.o +perf-y += llvm.o llvm-src-base.o llvm-src-kbuild.o llvm-src-prologue.o perf-y += bpf.o perf-y += topology.o @@ -49,6 +49,13 @@ $(OUTPUT)tests/llvm-src-kbuild.c: tests/bpf-script-test-kbuild.c $(Q)sed -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/g' $< >> $@ $(Q)echo ';' >> $@ +$(OUTPUT)tests/llvm-src-prologue.c: tests/bpf-script-test-prologue.c + $(call rule_mkdir) + $(Q)echo '#include ' > $@ + $(Q)echo 'const char test_llvm__bpf_test_prologue_prog[] =' >> $@ + $(Q)sed -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/g' $< >> $@ + $(Q)echo ';' >> $@ + ifeq ($(ARCH),$(filter $(ARCH),x86 arm arm64)) perf-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o endif diff --git a/tools/perf/tests/bpf-script-test-prologue.c b/tools/perf/tests/bpf-script-test-prologue.c new file mode 100644 index 000000000000..7230e62c70fc --- /dev/null +++ b/tools/perf/tests/bpf-script-test-prologue.c @@ -0,0 +1,35 @@ +/* + * bpf-script-test-prologue.c + * Test BPF prologue + */ +#ifndef LINUX_VERSION_CODE +# error Need LINUX_VERSION_CODE +# error Example: for 4.2 kernel, put 'clang-opt="-DLINUX_VERSION_CODE=0x40200" into llvm section of ~/.perfconfig' +#endif +#define SEC(NAME) __attribute__((section(NAME), used)) + +#include + +#define FMODE_READ 0x1 +#define FMODE_WRITE 0x2 + +static void (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) = + (void *) 6; + +SEC("func=null_lseek file->f_mode offset orig") +int bpf_func__null_lseek(void *ctx, int err, unsigned long f_mode, + unsigned long offset, unsigned long orig) +{ + if (err) + return 0; + if (f_mode & FMODE_WRITE) + return 0; + if (offset & 1) + return 0; + if (orig == SEEK_CUR) + return 0; + return 1; +} + +char _license[] SEC("license") = "GPL"; +int _version SEC("version") = LINUX_VERSION_CODE; diff --git a/tools/perf/tests/bpf.c b/tools/perf/tests/bpf.c index 6ebfdee3e2c6..d58442294e9e 100644 --- a/tools/perf/tests/bpf.c +++ b/tools/perf/tests/bpf.c @@ -19,6 +19,29 @@ static int epoll_pwait_loop(void) return 0; } +#ifdef HAVE_BPF_PROLOGUE + +static int llseek_loop(void) +{ + int fds[2], i; + + fds[0] = open("/dev/null", O_RDONLY); + fds[1] = open("/dev/null", O_RDWR); + + if (fds[0] < 0 || fds[1] < 0) + return -1; + + for (i = 0; i < NR_ITERS; i++) { + lseek(fds[i % 2], i, (i / 2) % 2 ? SEEK_CUR : SEEK_SET); + lseek(fds[(i + 1) % 2], i, (i / 2) % 2 ? SEEK_CUR : SEEK_SET); + } + close(fds[0]); + close(fds[1]); + return 0; +} + +#endif + static struct { enum test_llvm__testcase prog_id; const char *desc; @@ -37,6 +60,17 @@ static struct { &epoll_pwait_loop, (NR_ITERS + 1) / 2, }, +#ifdef HAVE_BPF_PROLOGUE + { + LLVM_TESTCASE_BPF_PROLOGUE, + "Test BPF prologue generation", + "[bpf_prologue_test]", + "fix kbuild first", + "check your vmlinux setting?", + &llseek_loop, + (NR_ITERS + 1) / 4, + }, +#endif }; static int do_test(struct bpf_object *obj, int (*func)(void), diff --git a/tools/perf/tests/llvm.c b/tools/perf/tests/llvm.c index 366e38ba8b49..b4147634fb44 100644 --- a/tools/perf/tests/llvm.c +++ b/tools/perf/tests/llvm.c @@ -44,6 +44,10 @@ static struct { .source = test_llvm__bpf_test_kbuild_prog, .desc = "Test kbuild searching", }, + [LLVM_TESTCASE_BPF_PROLOGUE] = { + .source = test_llvm__bpf_test_prologue_prog, + .desc = "Test BPF prologue generation", + }, }; diff --git a/tools/perf/tests/llvm.h b/tools/perf/tests/llvm.h index d91d8f44efee..5150b4d6ef50 100644 --- a/tools/perf/tests/llvm.h +++ b/tools/perf/tests/llvm.h @@ -6,10 +6,12 @@ extern const char test_llvm__bpf_base_prog[]; extern const char test_llvm__bpf_test_kbuild_prog[]; +extern const char test_llvm__bpf_test_prologue_prog[]; enum test_llvm__testcase { LLVM_TESTCASE_BASE, LLVM_TESTCASE_KBUILD, + LLVM_TESTCASE_BPF_PROLOGUE, __LLVM_TESTCASE_MAX, }; From ad0dd7aed5df8009b3ffa39bec73ad93283332c9 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Tue, 17 Nov 2015 08:32:46 +0000 Subject: [PATCH 14/37] perf test: Fix 'perf test BPF' when it fails to find a suitable vmlinux Two bugs in 'perf test BPF' are found when testing BPF prologue without vmlinux: # mv /lib/modules/4.3.0-rc4+/build/vmlinux{,.bak} # ./perf test BPF 37: Test BPF filter :Failed to find the path for kernel: No such file or directory Ok Test BPF should fail in this case. After this patch: # ./perf test BPF 37: Test BPF filter :Failed to find the path for kernel: No such file or directory FAILED! # mv /lib/modules/4.3.0-rc4+/build/vmlinux{.bak,} # ./perf test BPF 37: Test BPF filter : Ok Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447749170-175898-2-git-send-email-wangnan0@huawei.com Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/bpf.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/perf/tests/bpf.c b/tools/perf/tests/bpf.c index d58442294e9e..232043cc232a 100644 --- a/tools/perf/tests/bpf.c +++ b/tools/perf/tests/bpf.c @@ -102,8 +102,7 @@ static int do_test(struct bpf_object *obj, int (*func)(void), err = parse_events_load_bpf_obj(&parse_evlist, &parse_evlist.list, obj); if (err || list_empty(&parse_evlist.list)) { pr_debug("Failed to add events selected by BPF\n"); - if (!err) - return TEST_FAIL; + return TEST_FAIL; } snprintf(pid, sizeof(pid), "%d", getpid()); @@ -157,8 +156,10 @@ static int do_test(struct bpf_object *obj, int (*func)(void), } } - if (count != expect) + if (count != expect) { pr_debug("BPF filter result incorrect\n"); + goto out_delete_evlist; + } ret = TEST_OK; From d35b32891a61f1d3909bdc5280badf309adc4693 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Tue, 17 Nov 2015 08:32:47 +0000 Subject: [PATCH 15/37] perf bpf: Use same BPF program if arguments are identical This patch allows creating only one BPF program for different 'probe_trace_event'(tev) entries generated by one 'perf_probe_event'(pev) if their prologues are identical. This is done by comparing the argument list of different tev instances, and the maps type of prologue and tev using a mapping array. This patch utilizes qsort to sort the tevs. After sorting, tevs with identical argument lists will be grouped together. Test result: Sample BPF program: #define SEC(NAME) __attribute__((section(NAME), used)) SEC("inlines=no;" "func=SyS_dup? oldfd") int func(void *ctx) { return 1; } It would probe at SyS_dup2 and SyS_dup3, obtaining oldfd as its argument. The following cmdline shows a BPF program being loaded into the kernel by perf: # perf record -e ./test_bpf_arg.c sleep 4 & sleep 1 && ls /proc/$!/fd/ -l | grep bpf-prog Before this patch: # perf record -e ./test_bpf_arg.c sleep 4 & sleep 1 && ls /proc/$!/fd/ -l | grep bpf-prog [1] 24858 lrwx------ 1 root root 64 Nov 14 04:09 3 -> anon_inode:bpf-prog lrwx------ 1 root root 64 Nov 14 04:09 4 -> anon_inode:bpf-prog ... After this patch: # perf record -e ./test_bpf_arg.c sleep 4 & sleep 1 && ls /proc/$!/fd/ -l | grep bpf-prog [1] 25699 lrwx------ 1 root root 64 Nov 14 04:10 3 -> anon_inode:bpf-prog ... Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447749170-175898-3-git-send-email-wangnan0@huawei.com Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/bpf-loader.c | 138 +++++++++++++++++++++++++++++++++-- 1 file changed, 131 insertions(+), 7 deletions(-) diff --git a/tools/perf/util/bpf-loader.c b/tools/perf/util/bpf-loader.c index 190a1c7f0649..36544e5ece43 100644 --- a/tools/perf/util/bpf-loader.c +++ b/tools/perf/util/bpf-loader.c @@ -38,6 +38,8 @@ struct bpf_prog_priv { struct perf_probe_event pev; bool need_prologue; struct bpf_insn *insns_buf; + int nr_types; + int *type_mapping; }; static bool libbpf_initialized; @@ -113,6 +115,7 @@ bpf_prog_priv__clear(struct bpf_program *prog __maybe_unused, cleanup_perf_probe_events(&priv->pev, 1); zfree(&priv->insns_buf); + zfree(&priv->type_mapping); free(priv); } @@ -381,7 +384,7 @@ preproc_gen_prologue(struct bpf_program *prog, int n, struct bpf_prog_priv *priv; struct bpf_insn *buf; size_t prologue_cnt = 0; - int err; + int i, err; err = bpf_program__get_private(prog, (void **)&priv); if (err || !priv) @@ -389,10 +392,21 @@ preproc_gen_prologue(struct bpf_program *prog, int n, pev = &priv->pev; - if (n < 0 || n >= pev->ntevs) + if (n < 0 || n >= priv->nr_types) goto errout; - tev = &pev->tevs[n]; + /* Find a tev belongs to that type */ + for (i = 0; i < pev->ntevs; i++) { + if (priv->type_mapping[i] == n) + break; + } + + if (i >= pev->ntevs) { + pr_debug("Internal error: prologue type %d not found\n", n); + return -BPF_LOADER_ERRNO__PROLOGUE; + } + + tev = &pev->tevs[i]; buf = priv->insns_buf; err = bpf__gen_prologue(tev->args, tev->nargs, @@ -423,6 +437,101 @@ errout: return -BPF_LOADER_ERRNO__PROLOGUE; } +/* + * compare_tev_args is reflexive, transitive and antisymmetric. + * I can proof it but this margin is too narrow to contain. + */ +static int compare_tev_args(const void *ptev1, const void *ptev2) +{ + int i, ret; + const struct probe_trace_event *tev1 = + *(const struct probe_trace_event **)ptev1; + const struct probe_trace_event *tev2 = + *(const struct probe_trace_event **)ptev2; + + ret = tev2->nargs - tev1->nargs; + if (ret) + return ret; + + for (i = 0; i < tev1->nargs; i++) { + struct probe_trace_arg *arg1, *arg2; + struct probe_trace_arg_ref *ref1, *ref2; + + arg1 = &tev1->args[i]; + arg2 = &tev2->args[i]; + + ret = strcmp(arg1->value, arg2->value); + if (ret) + return ret; + + ref1 = arg1->ref; + ref2 = arg2->ref; + + while (ref1 && ref2) { + ret = ref2->offset - ref1->offset; + if (ret) + return ret; + + ref1 = ref1->next; + ref2 = ref2->next; + } + + if (ref1 || ref2) + return ref2 ? 1 : -1; + } + + return 0; +} + +/* + * Assign a type number to each tevs in a pev. + * mapping is an array with same slots as tevs in that pev. + * nr_types will be set to number of types. + */ +static int map_prologue(struct perf_probe_event *pev, int *mapping, + int *nr_types) +{ + int i, type = 0; + struct probe_trace_event **ptevs; + + size_t array_sz = sizeof(*ptevs) * pev->ntevs; + + ptevs = malloc(array_sz); + if (!ptevs) { + pr_debug("No ehough memory: alloc ptevs failed\n"); + return -ENOMEM; + } + + pr_debug("In map_prologue, ntevs=%d\n", pev->ntevs); + for (i = 0; i < pev->ntevs; i++) + ptevs[i] = &pev->tevs[i]; + + qsort(ptevs, pev->ntevs, sizeof(*ptevs), + compare_tev_args); + + for (i = 0; i < pev->ntevs; i++) { + int n; + + n = ptevs[i] - pev->tevs; + if (i == 0) { + mapping[n] = type; + pr_debug("mapping[%d]=%d\n", n, type); + continue; + } + + if (compare_tev_args(ptevs + i, ptevs + i - 1) == 0) + mapping[n] = type; + else + mapping[n] = ++type; + + pr_debug("mapping[%d]=%d\n", n, mapping[n]); + } + free(ptevs); + *nr_types = type + 1; + + return 0; +} + static int hook_load_preprocessor(struct bpf_program *prog) { struct perf_probe_event *pev; @@ -462,7 +571,19 @@ static int hook_load_preprocessor(struct bpf_program *prog) return -ENOMEM; } - err = bpf_program__set_prep(prog, pev->ntevs, + priv->type_mapping = malloc(sizeof(int) * pev->ntevs); + if (!priv->type_mapping) { + pr_debug("No enough memory: alloc type_mapping failed\n"); + return -ENOMEM; + } + memset(priv->type_mapping, -1, + sizeof(int) * pev->ntevs); + + err = map_prologue(pev, priv->type_mapping, &priv->nr_types); + if (err) + return err; + + err = bpf_program__set_prep(prog, priv->nr_types, preproc_gen_prologue); return err; } @@ -596,10 +717,13 @@ int bpf__foreach_tev(struct bpf_object *obj, for (i = 0; i < pev->ntevs; i++) { tev = &pev->tevs[i]; - if (priv->need_prologue) - fd = bpf_program__nth_fd(prog, i); - else + if (priv->need_prologue) { + int type = priv->type_mapping[i]; + + fd = bpf_program__nth_fd(prog, type); + } else { fd = bpf_program__fd(prog); + } if (fd < 0) { pr_debug("bpf: failed to get file descriptor\n"); From 721a1f53df6aad3ea941f5fe95519d0d8e02bd65 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 19 Nov 2015 12:01:48 -0300 Subject: [PATCH 16/37] perf tests: Pass the subtest index to each test routine Some tests have sub-tests we want to run, so allow passing this. Wang tried to avoid having to touch all tests, but then, having the test.func in an anonymous union makes the build fail on older compilers, like the one in RHEL6, where: test a = { .func = foo, }; fails. To fix it leave the func pointer in the main structure and pass the subtest index to all tests, end result function is the same, but we have just one function pointer, not two, with and without the subtest index as an argument. Cc: Adrian Hunter Cc: David Ahern Cc: Jiri Olsa Cc: Namhyung Kim Cc: Wang Nan Link: http://lkml.kernel.org/n/tip-5genj0ficwdmelpoqlds0u4y@git.kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/arch/x86/include/arch-tests.h | 8 +- tools/perf/arch/x86/tests/insn-x86.c | 2 +- tools/perf/arch/x86/tests/intel-cqm.c | 2 +- tools/perf/arch/x86/tests/perf-time-to-tsc.c | 2 +- tools/perf/arch/x86/tests/rdpmc.c | 2 +- tools/perf/tests/attr.c | 2 +- tools/perf/tests/bp_signal.c | 2 +- tools/perf/tests/bp_signal_overflow.c | 2 +- tools/perf/tests/bpf.c | 2 +- tools/perf/tests/builtin-test.c | 6 +- tools/perf/tests/code-reading.c | 2 +- tools/perf/tests/dso-data.c | 6 +- tools/perf/tests/dwarf-unwind.c | 2 +- tools/perf/tests/evsel-roundtrip-name.c | 2 +- tools/perf/tests/evsel-tp-sched.c | 2 +- tools/perf/tests/fdarray.c | 4 +- tools/perf/tests/hists_cumulate.c | 2 +- tools/perf/tests/hists_filter.c | 2 +- tools/perf/tests/hists_link.c | 2 +- tools/perf/tests/hists_output.c | 2 +- tools/perf/tests/keep-tracking.c | 2 +- tools/perf/tests/kmod-path.c | 2 +- tools/perf/tests/llvm.c | 2 +- tools/perf/tests/mmap-basic.c | 2 +- tools/perf/tests/mmap-thread-lookup.c | 2 +- tools/perf/tests/openat-syscall-all-cpus.c | 2 +- tools/perf/tests/openat-syscall-tp-fields.c | 2 +- tools/perf/tests/openat-syscall.c | 2 +- tools/perf/tests/parse-events.c | 2 +- tools/perf/tests/parse-no-sample-id-all.c | 2 +- tools/perf/tests/perf-record.c | 2 +- tools/perf/tests/pmu.c | 2 +- tools/perf/tests/python-use.c | 3 +- tools/perf/tests/sample-parsing.c | 2 +- tools/perf/tests/sw-clock.c | 2 +- tools/perf/tests/switch-tracking.c | 2 +- tools/perf/tests/task-exit.c | 2 +- tools/perf/tests/tests.h | 78 ++++++++++---------- tools/perf/tests/thread-map.c | 2 +- tools/perf/tests/thread-mg-share.c | 2 +- tools/perf/tests/topology.c | 2 +- tools/perf/tests/vmlinux-kallsyms.c | 2 +- 42 files changed, 89 insertions(+), 88 deletions(-) diff --git a/tools/perf/arch/x86/include/arch-tests.h b/tools/perf/arch/x86/include/arch-tests.h index 7ed00f4b0908..b48de2f5813c 100644 --- a/tools/perf/arch/x86/include/arch-tests.h +++ b/tools/perf/arch/x86/include/arch-tests.h @@ -2,10 +2,10 @@ #define ARCH_TESTS_H /* Tests */ -int test__rdpmc(void); -int test__perf_time_to_tsc(void); -int test__insn_x86(void); -int test__intel_cqm_count_nmi_context(void); +int test__rdpmc(int subtest); +int test__perf_time_to_tsc(int subtest); +int test__insn_x86(int subtest); +int test__intel_cqm_count_nmi_context(int subtest); #ifdef HAVE_DWARF_UNWIND_SUPPORT struct thread; diff --git a/tools/perf/arch/x86/tests/insn-x86.c b/tools/perf/arch/x86/tests/insn-x86.c index b6115dfd28f0..08d9b2bc185c 100644 --- a/tools/perf/arch/x86/tests/insn-x86.c +++ b/tools/perf/arch/x86/tests/insn-x86.c @@ -171,7 +171,7 @@ static int test_data_set(struct test_data *dat_set, int x86_64) * verbose (-v) option to see all the instructions and whether or not they * decoded successfuly. */ -int test__insn_x86(void) +int test__insn_x86(int subtest __maybe_unused) { int ret = 0; diff --git a/tools/perf/arch/x86/tests/intel-cqm.c b/tools/perf/arch/x86/tests/intel-cqm.c index d28c1b6a3b54..94e0cb7462f9 100644 --- a/tools/perf/arch/x86/tests/intel-cqm.c +++ b/tools/perf/arch/x86/tests/intel-cqm.c @@ -33,7 +33,7 @@ static pid_t spawn(void) * the last read counter value to avoid triggering a WARN_ON_ONCE() in * smp_call_function_many() caused by sending IPIs from NMI context. */ -int test__intel_cqm_count_nmi_context(void) +int test__intel_cqm_count_nmi_context(int subtest __maybe_unused) { struct perf_evlist *evlist = NULL; struct perf_evsel *evsel = NULL; diff --git a/tools/perf/arch/x86/tests/perf-time-to-tsc.c b/tools/perf/arch/x86/tests/perf-time-to-tsc.c index 658cd200af74..a289aa8a083a 100644 --- a/tools/perf/arch/x86/tests/perf-time-to-tsc.c +++ b/tools/perf/arch/x86/tests/perf-time-to-tsc.c @@ -35,7 +35,7 @@ * %0 is returned, otherwise %-1 is returned. If TSC conversion is not * supported then then the test passes but " (not supported)" is printed. */ -int test__perf_time_to_tsc(void) +int test__perf_time_to_tsc(int subtest __maybe_unused) { struct record_opts opts = { .mmap_pages = UINT_MAX, diff --git a/tools/perf/arch/x86/tests/rdpmc.c b/tools/perf/arch/x86/tests/rdpmc.c index e7688214c7cf..7bb0d13c235f 100644 --- a/tools/perf/arch/x86/tests/rdpmc.c +++ b/tools/perf/arch/x86/tests/rdpmc.c @@ -149,7 +149,7 @@ out_close: return 0; } -int test__rdpmc(void) +int test__rdpmc(int subtest __maybe_unused) { int status = 0; int wret = 0; diff --git a/tools/perf/tests/attr.c b/tools/perf/tests/attr.c index 638875a0960a..b66730eb94e3 100644 --- a/tools/perf/tests/attr.c +++ b/tools/perf/tests/attr.c @@ -153,7 +153,7 @@ static int run_dir(const char *d, const char *perf) return system(cmd); } -int test__attr(void) +int test__attr(int subtest __maybe_unused) { struct stat st; char path_perf[PATH_MAX]; diff --git a/tools/perf/tests/bp_signal.c b/tools/perf/tests/bp_signal.c index a02b035fd5aa..fb80c9eb6a95 100644 --- a/tools/perf/tests/bp_signal.c +++ b/tools/perf/tests/bp_signal.c @@ -111,7 +111,7 @@ static long long bp_count(int fd) return count; } -int test__bp_signal(void) +int test__bp_signal(int subtest __maybe_unused) { struct sigaction sa; long long count1, count2; diff --git a/tools/perf/tests/bp_signal_overflow.c b/tools/perf/tests/bp_signal_overflow.c index e76537724491..89f92fa67cc4 100644 --- a/tools/perf/tests/bp_signal_overflow.c +++ b/tools/perf/tests/bp_signal_overflow.c @@ -58,7 +58,7 @@ static long long bp_count(int fd) #define EXECUTIONS 10000 #define THRESHOLD 100 -int test__bp_signal_overflow(void) +int test__bp_signal_overflow(int subtest __maybe_unused) { struct perf_event_attr pe; struct sigaction sa; diff --git a/tools/perf/tests/bpf.c b/tools/perf/tests/bpf.c index 232043cc232a..4efdc1607754 100644 --- a/tools/perf/tests/bpf.c +++ b/tools/perf/tests/bpf.c @@ -215,7 +215,7 @@ out: return ret; } -int test__bpf(void) +int test__bpf(int subtest __maybe_unused) { unsigned int i; int err; diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 80c442eab767..9cf4892c061d 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -203,7 +203,7 @@ static bool perf_test__matches(struct test *test, int curr, int argc, const char return false; } -static int run_test(struct test *test) +static int run_test(struct test *test, int subtest) { int status, err = -1, child = fork(); char sbuf[STRERR_BUFSIZE]; @@ -216,7 +216,7 @@ static int run_test(struct test *test) if (!child) { pr_debug("test child forked, pid %d\n", getpid()); - err = test->func(); + err = test->func(subtest); exit(err); } @@ -265,7 +265,7 @@ static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) } pr_debug("\n--- start ---\n"); - err = run_test(t); + err = run_test(t, i); pr_debug("---- end ----\n%s:", t->desc); switch (err) { diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c index a767a6400c5c..4417b6a079f0 100644 --- a/tools/perf/tests/code-reading.c +++ b/tools/perf/tests/code-reading.c @@ -601,7 +601,7 @@ out_err: return err; } -int test__code_reading(void) +int test__code_reading(int subtest __maybe_unused) { int ret; diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index a218aeaf56a0..dc673ff7c437 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -110,7 +110,7 @@ static int dso__data_fd(struct dso *dso, struct machine *machine) return fd; } -int test__dso_data(void) +int test__dso_data(int subtest __maybe_unused) { struct machine machine; struct dso *dso; @@ -245,7 +245,7 @@ static int set_fd_limit(int n) return setrlimit(RLIMIT_NOFILE, &rlim); } -int test__dso_data_cache(void) +int test__dso_data_cache(int subtest __maybe_unused) { struct machine machine; long nr_end, nr = open_files_cnt(); @@ -302,7 +302,7 @@ int test__dso_data_cache(void) return 0; } -int test__dso_data_reopen(void) +int test__dso_data_reopen(int subtest __maybe_unused) { struct machine machine; long nr_end, nr = open_files_cnt(); diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c index 07221793a3ac..01f0b61de53d 100644 --- a/tools/perf/tests/dwarf-unwind.c +++ b/tools/perf/tests/dwarf-unwind.c @@ -142,7 +142,7 @@ static int krava_1(struct thread *thread) return krava_2(thread); } -int test__dwarf_unwind(void) +int test__dwarf_unwind(int subtest __maybe_unused) { struct machines machines; struct machine *machine; diff --git a/tools/perf/tests/evsel-roundtrip-name.c b/tools/perf/tests/evsel-roundtrip-name.c index 3fa715987a5e..1da92e1159ee 100644 --- a/tools/perf/tests/evsel-roundtrip-name.c +++ b/tools/perf/tests/evsel-roundtrip-name.c @@ -95,7 +95,7 @@ out_delete_evlist: #define perf_evsel__name_array_test(names) \ __perf_evsel__name_array_test(names, ARRAY_SIZE(names)) -int test__perf_evsel__roundtrip_name_test(void) +int test__perf_evsel__roundtrip_name_test(int subtest __maybe_unused) { int err = 0, ret = 0; diff --git a/tools/perf/tests/evsel-tp-sched.c b/tools/perf/tests/evsel-tp-sched.c index 790e413d9a1f..1984b3bbfe15 100644 --- a/tools/perf/tests/evsel-tp-sched.c +++ b/tools/perf/tests/evsel-tp-sched.c @@ -32,7 +32,7 @@ static int perf_evsel__test_field(struct perf_evsel *evsel, const char *name, return ret; } -int test__perf_evsel__tp_sched_test(void) +int test__perf_evsel__tp_sched_test(int subtest __maybe_unused) { struct perf_evsel *evsel = perf_evsel__newtp("sched", "sched_switch"); int ret = 0; diff --git a/tools/perf/tests/fdarray.c b/tools/perf/tests/fdarray.c index d24b837951d4..c809463edbe5 100644 --- a/tools/perf/tests/fdarray.c +++ b/tools/perf/tests/fdarray.c @@ -25,7 +25,7 @@ static int fdarray__fprintf_prefix(struct fdarray *fda, const char *prefix, FILE return printed + fdarray__fprintf(fda, fp); } -int test__fdarray__filter(void) +int test__fdarray__filter(int subtest __maybe_unused) { int nr_fds, expected_fd[2], fd, err = TEST_FAIL; struct fdarray *fda = fdarray__new(5, 5); @@ -103,7 +103,7 @@ out: return err; } -int test__fdarray__add(void) +int test__fdarray__add(int subtest __maybe_unused) { int err = TEST_FAIL; struct fdarray *fda = fdarray__new(2, 2); diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c index 7ed737019de7..8292948bc5f9 100644 --- a/tools/perf/tests/hists_cumulate.c +++ b/tools/perf/tests/hists_cumulate.c @@ -686,7 +686,7 @@ out: return err; } -int test__hists_cumulate(void) +int test__hists_cumulate(int subtest __maybe_unused) { int err = TEST_FAIL; struct machines machines; diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c index 818acf875dd0..ccb5b4921f25 100644 --- a/tools/perf/tests/hists_filter.c +++ b/tools/perf/tests/hists_filter.c @@ -104,7 +104,7 @@ out: return TEST_FAIL; } -int test__hists_filter(void) +int test__hists_filter(int subtest __maybe_unused) { int err = TEST_FAIL; struct machines machines; diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c index 8c102b011424..6243e2b2a245 100644 --- a/tools/perf/tests/hists_link.c +++ b/tools/perf/tests/hists_link.c @@ -274,7 +274,7 @@ static int validate_link(struct hists *leader, struct hists *other) return __validate_link(leader, 0) || __validate_link(other, 1); } -int test__hists_link(void) +int test__hists_link(int subtest __maybe_unused) { int err = -1; struct hists *hists, *first_hists; diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c index adbebc852cc8..248beec1d917 100644 --- a/tools/perf/tests/hists_output.c +++ b/tools/perf/tests/hists_output.c @@ -576,7 +576,7 @@ out: return err; } -int test__hists_output(void) +int test__hists_output(int subtest __maybe_unused) { int err = TEST_FAIL; struct machines machines; diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c index a2e2269aa093..a337a6da1f39 100644 --- a/tools/perf/tests/keep-tracking.c +++ b/tools/perf/tests/keep-tracking.c @@ -49,7 +49,7 @@ static int find_comm(struct perf_evlist *evlist, const char *comm) * when an event is disabled but a dummy software event is not disabled. If the * test passes %0 is returned, otherwise %-1 is returned. */ -int test__keep_tracking(void) +int test__keep_tracking(int subtest __maybe_unused) { struct record_opts opts = { .mmap_pages = UINT_MAX, diff --git a/tools/perf/tests/kmod-path.c b/tools/perf/tests/kmod-path.c index 08c433b4bf4f..d2af78193153 100644 --- a/tools/perf/tests/kmod-path.c +++ b/tools/perf/tests/kmod-path.c @@ -49,7 +49,7 @@ static int test_is_kernel_module(const char *path, int cpumode, bool expect) #define M(path, c, e) \ TEST_ASSERT_VAL("failed", !test_is_kernel_module(path, c, e)) -int test__kmod_path__parse(void) +int test__kmod_path__parse(int subtest __maybe_unused) { /* path alloc_name alloc_ext kmod comp name ext */ T("/xxxx/xxxx/x-x.ko", true , true , true, false, "[x_x]", NULL); diff --git a/tools/perf/tests/llvm.c b/tools/perf/tests/llvm.c index b4147634fb44..4350c455d06c 100644 --- a/tools/perf/tests/llvm.c +++ b/tools/perf/tests/llvm.c @@ -131,7 +131,7 @@ out: return ret; } -int test__llvm(void) +int test__llvm(int subtest __maybe_unused) { enum test_llvm__testcase i; diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c index 4495493c9431..359e98fcd94c 100644 --- a/tools/perf/tests/mmap-basic.c +++ b/tools/perf/tests/mmap-basic.c @@ -16,7 +16,7 @@ * Then it checks if the number of syscalls reported as perf events by * the kernel corresponds to the number of syscalls made. */ -int test__basic_mmap(void) +int test__basic_mmap(int subtest __maybe_unused) { int err = -1; union perf_event *event; diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c index 145050e2e544..6cdb97579c45 100644 --- a/tools/perf/tests/mmap-thread-lookup.c +++ b/tools/perf/tests/mmap-thread-lookup.c @@ -221,7 +221,7 @@ static int mmap_events(synth_cb synth) * * by using all thread objects. */ -int test__mmap_thread_lookup(void) +int test__mmap_thread_lookup(int subtest __maybe_unused) { /* perf_event__synthesize_threads synthesize */ TEST_ASSERT_VAL("failed with sythesizing all", diff --git a/tools/perf/tests/openat-syscall-all-cpus.c b/tools/perf/tests/openat-syscall-all-cpus.c index 2006485a2859..53c2273e8859 100644 --- a/tools/perf/tests/openat-syscall-all-cpus.c +++ b/tools/perf/tests/openat-syscall-all-cpus.c @@ -7,7 +7,7 @@ #include "debug.h" #include "stat.h" -int test__openat_syscall_event_on_all_cpus(void) +int test__openat_syscall_event_on_all_cpus(int subtest __maybe_unused) { int err = -1, fd, cpu; struct cpu_map *cpus; diff --git a/tools/perf/tests/openat-syscall-tp-fields.c b/tools/perf/tests/openat-syscall-tp-fields.c index 5e811cd8f1c3..eb99a105f31c 100644 --- a/tools/perf/tests/openat-syscall-tp-fields.c +++ b/tools/perf/tests/openat-syscall-tp-fields.c @@ -6,7 +6,7 @@ #include "tests.h" #include "debug.h" -int test__syscall_openat_tp_fields(void) +int test__syscall_openat_tp_fields(int subtest __maybe_unused) { struct record_opts opts = { .target = { diff --git a/tools/perf/tests/openat-syscall.c b/tools/perf/tests/openat-syscall.c index 033b54797b8a..1184f9ba6499 100644 --- a/tools/perf/tests/openat-syscall.c +++ b/tools/perf/tests/openat-syscall.c @@ -5,7 +5,7 @@ #include "debug.h" #include "tests.h" -int test__openat_syscall_event(void) +int test__openat_syscall_event(int subtest __maybe_unused) { int err = -1, fd; struct perf_evsel *evsel; diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c index 636d7b42d844..abe8849d1d70 100644 --- a/tools/perf/tests/parse-events.c +++ b/tools/perf/tests/parse-events.c @@ -1765,7 +1765,7 @@ static void debug_warn(const char *warn, va_list params) fprintf(stderr, " Warning: %s\n", msg); } -int test__parse_events(void) +int test__parse_events(int subtest __maybe_unused) { int ret1, ret2 = 0; diff --git a/tools/perf/tests/parse-no-sample-id-all.c b/tools/perf/tests/parse-no-sample-id-all.c index 2c63ea658541..294c76b01b41 100644 --- a/tools/perf/tests/parse-no-sample-id-all.c +++ b/tools/perf/tests/parse-no-sample-id-all.c @@ -67,7 +67,7 @@ struct test_attr_event { * * Return: %0 on success, %-1 if the test fails. */ -int test__parse_no_sample_id_all(void) +int test__parse_no_sample_id_all(int subtest __maybe_unused) { int err; diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c index 7a228a2a070b..9d5f0b57c4c1 100644 --- a/tools/perf/tests/perf-record.c +++ b/tools/perf/tests/perf-record.c @@ -32,7 +32,7 @@ realloc: return cpu; } -int test__PERF_RECORD(void) +int test__PERF_RECORD(int subtest __maybe_unused) { struct record_opts opts = { .target = { diff --git a/tools/perf/tests/pmu.c b/tools/perf/tests/pmu.c index faa04e9d5d5f..1e2ba2602930 100644 --- a/tools/perf/tests/pmu.c +++ b/tools/perf/tests/pmu.c @@ -133,7 +133,7 @@ static struct list_head *test_terms_list(void) return &terms; } -int test__pmu(void) +int test__pmu(int subtest __maybe_unused) { char *format = test_format_dir_get(); LIST_HEAD(formats); diff --git a/tools/perf/tests/python-use.c b/tools/perf/tests/python-use.c index 7760277c6def..7a52834ee0d0 100644 --- a/tools/perf/tests/python-use.c +++ b/tools/perf/tests/python-use.c @@ -4,11 +4,12 @@ #include #include +#include #include "tests.h" extern int verbose; -int test__python_use(void) +int test__python_use(int subtest __maybe_unused) { char *cmd; int ret; diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c index 30c02181e78b..5f23710b9fee 100644 --- a/tools/perf/tests/sample-parsing.c +++ b/tools/perf/tests/sample-parsing.c @@ -290,7 +290,7 @@ out_free: * checks sample format bits separately and together. If the test passes %0 is * returned, otherwise %-1 is returned. */ -int test__sample_parsing(void) +int test__sample_parsing(int subtest __maybe_unused) { const u64 rf[] = {4, 5, 6, 7, 12, 13, 14, 15}; u64 sample_type; diff --git a/tools/perf/tests/sw-clock.c b/tools/perf/tests/sw-clock.c index 5b83f56a3b6f..36e8ce1550e3 100644 --- a/tools/perf/tests/sw-clock.c +++ b/tools/perf/tests/sw-clock.c @@ -122,7 +122,7 @@ out_delete_evlist: return err; } -int test__sw_clock_freq(void) +int test__sw_clock_freq(int subtest __maybe_unused) { int ret; diff --git a/tools/perf/tests/switch-tracking.c b/tools/perf/tests/switch-tracking.c index a02af503100c..dfbd8d69ce89 100644 --- a/tools/perf/tests/switch-tracking.c +++ b/tools/perf/tests/switch-tracking.c @@ -305,7 +305,7 @@ out_free_nodes: * evsel->system_wide and evsel->tracking flags (respectively) with other events * sometimes enabled or disabled. */ -int test__switch_tracking(void) +int test__switch_tracking(int subtest __maybe_unused) { const char *sched_switch = "sched:sched_switch"; struct switch_tracking switch_tracking = { .tids = NULL, }; diff --git a/tools/perf/tests/task-exit.c b/tools/perf/tests/task-exit.c index add16385f13e..2dfff7ac8ef3 100644 --- a/tools/perf/tests/task-exit.c +++ b/tools/perf/tests/task-exit.c @@ -31,7 +31,7 @@ static void workload_exec_failed_signal(int signo __maybe_unused, * if the number of exit event reported by the kernel is 1 or not * in order to check the kernel returns correct number of event. */ -int test__task_exit(void) +int test__task_exit(int subtest __maybe_unused) { int err = -1; union perf_event *event; diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index 3c8734a3abbc..204e4eeadea2 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -26,48 +26,48 @@ enum { struct test { const char *desc; - int (*func)(void); + int (*func)(int subtest); }; /* Tests */ -int test__vmlinux_matches_kallsyms(void); -int test__openat_syscall_event(void); -int test__openat_syscall_event_on_all_cpus(void); -int test__basic_mmap(void); -int test__PERF_RECORD(void); -int test__perf_evsel__roundtrip_name_test(void); -int test__perf_evsel__tp_sched_test(void); -int test__syscall_openat_tp_fields(void); -int test__pmu(void); -int test__attr(void); -int test__dso_data(void); -int test__dso_data_cache(void); -int test__dso_data_reopen(void); -int test__parse_events(void); -int test__hists_link(void); -int test__python_use(void); -int test__bp_signal(void); -int test__bp_signal_overflow(void); -int test__task_exit(void); -int test__sw_clock_freq(void); -int test__code_reading(void); -int test__sample_parsing(void); -int test__keep_tracking(void); -int test__parse_no_sample_id_all(void); -int test__dwarf_unwind(void); -int test__hists_filter(void); -int test__mmap_thread_lookup(void); -int test__thread_mg_share(void); -int test__hists_output(void); -int test__hists_cumulate(void); -int test__switch_tracking(void); -int test__fdarray__filter(void); -int test__fdarray__add(void); -int test__kmod_path__parse(void); -int test__thread_map(void); -int test__llvm(void); -int test__bpf(void); -int test_session_topology(void); +int test__vmlinux_matches_kallsyms(int subtest); +int test__openat_syscall_event(int subtest); +int test__openat_syscall_event_on_all_cpus(int subtest); +int test__basic_mmap(int subtest); +int test__PERF_RECORD(int subtest); +int test__perf_evsel__roundtrip_name_test(int subtest); +int test__perf_evsel__tp_sched_test(int subtest); +int test__syscall_openat_tp_fields(int subtest); +int test__pmu(int subtest); +int test__attr(int subtest); +int test__dso_data(int subtest); +int test__dso_data_cache(int subtest); +int test__dso_data_reopen(int subtest); +int test__parse_events(int subtest); +int test__hists_link(int subtest); +int test__python_use(int subtest); +int test__bp_signal(int subtest); +int test__bp_signal_overflow(int subtest); +int test__task_exit(int subtest); +int test__sw_clock_freq(int subtest); +int test__code_reading(int subtest); +int test__sample_parsing(int subtest); +int test__keep_tracking(int subtest); +int test__parse_no_sample_id_all(int subtest); +int test__dwarf_unwind(int subtest); +int test__hists_filter(int subtest); +int test__mmap_thread_lookup(int subtest); +int test__thread_mg_share(int subtest); +int test__hists_output(int subtest); +int test__hists_cumulate(int subtest); +int test__switch_tracking(int subtest); +int test__fdarray__filter(int subtest); +int test__fdarray__add(int subtest); +int test__kmod_path__parse(int subtest); +int test__thread_map(int subtest); +int test__llvm(int subtest); +int test__bpf(int subtest); +int test_session_topology(int subtest); #if defined(__arm__) || defined(__aarch64__) #ifdef HAVE_DWARF_UNWIND_SUPPORT diff --git a/tools/perf/tests/thread-map.c b/tools/perf/tests/thread-map.c index 138a0e3431fa..2be02d303e82 100644 --- a/tools/perf/tests/thread-map.c +++ b/tools/perf/tests/thread-map.c @@ -4,7 +4,7 @@ #include "thread_map.h" #include "debug.h" -int test__thread_map(void) +int test__thread_map(int subtest __maybe_unused) { struct thread_map *map; diff --git a/tools/perf/tests/thread-mg-share.c b/tools/perf/tests/thread-mg-share.c index 01fabb19d746..188b63140fc8 100644 --- a/tools/perf/tests/thread-mg-share.c +++ b/tools/perf/tests/thread-mg-share.c @@ -4,7 +4,7 @@ #include "map.h" #include "debug.h" -int test__thread_mg_share(void) +int test__thread_mg_share(int subtest __maybe_unused) { struct machines machines; struct machine *machine; diff --git a/tools/perf/tests/topology.c b/tools/perf/tests/topology.c index f5bb096c3bd9..98fe69ac553c 100644 --- a/tools/perf/tests/topology.c +++ b/tools/perf/tests/topology.c @@ -84,7 +84,7 @@ static int check_cpu_topology(char *path, struct cpu_map *map) return 0; } -int test_session_topology(void) +int test_session_topology(int subtest __maybe_unused) { char path[PATH_MAX]; struct cpu_map *map; diff --git a/tools/perf/tests/vmlinux-kallsyms.c b/tools/perf/tests/vmlinux-kallsyms.c index d677e018e504..f0bfc9e8fd9f 100644 --- a/tools/perf/tests/vmlinux-kallsyms.c +++ b/tools/perf/tests/vmlinux-kallsyms.c @@ -18,7 +18,7 @@ static int vmlinux_matches_kallsyms_filter(struct map *map __maybe_unused, #define UM(x) kallsyms_map->unmap_ip(kallsyms_map, (x)) -int test__vmlinux_matches_kallsyms(void) +int test__vmlinux_matches_kallsyms(int subtest __maybe_unused) { int err = -1; struct rb_node *nd; From e8c6d500447c577e669c24ec04cd4173fe9f9afb Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Tue, 17 Nov 2015 08:32:48 +0000 Subject: [PATCH 17/37] perf test: Print result for each LLVM subtest Currently 'perf test llvm' and 'perf test BPF' have multiple sub-tests, but the result is provided in only one line: # perf test LLVM 35: Test LLVM searching and compiling : Ok This patch introduces sub-tests support, allowing 'perf test' to report result for each sub-tests: # perf test LLVM 35: Test LLVM searching and compiling : 35.1: Basic BPF llvm compiling test : Ok 35.2: Test kbuild searching : Ok 35.3: Compile source for BPF prologue generation test : Ok When a failure happens: # cat ~/.perfconfig [llvm] clang-path = "/bin/false" # perf test LLVM 35: Test LLVM searching and compiling : 35.1: Basic BPF llvm compiling test : FAILED! 35.2: Test kbuild searching : Skip 35.3: Compile source for BPF prologue generation test : Skip And: # rm ~/.perfconfig # ./perf test LLVM 35: Test LLVM searching and compiling : 35.1: Basic BPF llvm compiling test : Skip 35.2: Test kbuild searching : Skip 35.3: Compile source for BPF prologue generation test : Skip Skip by user: # ./perf test -s 1,`seq -s , 3 42` 1: vmlinux symtab matches kallsyms : Skip (user override) 2: detect openat syscall event : Ok ... 35: Test LLVM searching and compiling : Skip (user override) ... Suggested-and-Tested-by: Arnaldo Carvalho de Melo Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447749170-175898-4-git-send-email-wangnan0@huawei.com [ Changed so that func is not on an anonymous union ] Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/builtin-test.c | 89 +++++++++++++++++++++++++++------ tools/perf/tests/llvm.c | 65 +++++++++++------------- tools/perf/tests/tests.h | 9 ++++ 3 files changed, 114 insertions(+), 49 deletions(-) diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 9cf4892c061d..813660976217 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -160,6 +160,11 @@ static struct test generic_tests[] = { { .desc = "Test LLVM searching and compiling", .func = test__llvm, + .subtest = { + .skip_if_fail = true, + .get_nr = test__llvm_subtest_get_nr, + .get_desc = test__llvm_subtest_get_desc, + }, }, { .desc = "Test topology in session", @@ -237,6 +242,40 @@ static int run_test(struct test *test, int subtest) for (j = 0; j < ARRAY_SIZE(tests); j++) \ for (t = &tests[j][0]; t->func; t++) +static int test_and_print(struct test *t, bool force_skip, int subtest) +{ + int err; + + if (!force_skip) { + pr_debug("\n--- start ---\n"); + err = run_test(t, subtest); + pr_debug("---- end ----\n"); + } else { + pr_debug("\n--- force skipped ---\n"); + err = TEST_SKIP; + } + + if (!t->subtest.get_nr) + pr_debug("%s:", t->desc); + else + pr_debug("%s subtest %d:", t->desc, subtest); + + switch (err) { + case TEST_OK: + pr_info(" Ok\n"); + break; + case TEST_SKIP: + color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip\n"); + break; + case TEST_FAIL: + default: + color_fprintf(stderr, PERF_COLOR_RED, " FAILED!\n"); + break; + } + + return err; +} + static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) { struct test *t; @@ -264,21 +303,43 @@ static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) continue; } - pr_debug("\n--- start ---\n"); - err = run_test(t, i); - pr_debug("---- end ----\n%s:", t->desc); + if (!t->subtest.get_nr) { + test_and_print(t, false, -1); + } else { + int subn = t->subtest.get_nr(); + /* + * minus 2 to align with normal testcases. + * For subtest we print additional '.x' in number. + * for example: + * + * 35: Test LLVM searching and compiling : + * 35.1: Basic BPF llvm compiling test : Ok + */ + int subw = width > 2 ? width - 2 : width; + bool skip = false; + int subi; - switch (err) { - case TEST_OK: - pr_info(" Ok\n"); - break; - case TEST_SKIP: - color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip\n"); - break; - case TEST_FAIL: - default: - color_fprintf(stderr, PERF_COLOR_RED, " FAILED!\n"); - break; + if (subn <= 0) { + color_fprintf(stderr, PERF_COLOR_YELLOW, + " Skip (not compiled in)\n"); + continue; + } + pr_info("\n"); + + for (subi = 0; subi < subn; subi++) { + int len = strlen(t->subtest.get_desc(subi)); + + if (subw < len) + subw = len; + } + + for (subi = 0; subi < subn; subi++) { + pr_info("%2d.%1d: %-*s:", i, subi + 1, subw, + t->subtest.get_desc(subi)); + err = test_and_print(t, skip, subi); + if (err != TEST_OK && t->subtest.skip_if_fail) + skip = true; + } } } diff --git a/tools/perf/tests/llvm.c b/tools/perf/tests/llvm.c index 4350c455d06c..06f45c1d4256 100644 --- a/tools/perf/tests/llvm.c +++ b/tools/perf/tests/llvm.c @@ -46,7 +46,7 @@ static struct { }, [LLVM_TESTCASE_BPF_PROLOGUE] = { .source = test_llvm__bpf_test_prologue_prog, - .desc = "Test BPF prologue generation", + .desc = "Compile source for BPF prologue generation test", }, }; @@ -131,44 +131,39 @@ out: return ret; } -int test__llvm(int subtest __maybe_unused) +int test__llvm(int subtest) { - enum test_llvm__testcase i; + int ret; + void *obj_buf = NULL; + size_t obj_buf_sz = 0; - for (i = 0; i < __LLVM_TESTCASE_MAX; i++) { - int ret; - void *obj_buf = NULL; - size_t obj_buf_sz = 0; + if ((subtest < 0) || (subtest >= __LLVM_TESTCASE_MAX)) + return TEST_FAIL; - ret = test_llvm__fetch_bpf_obj(&obj_buf, &obj_buf_sz, - i, false); + ret = test_llvm__fetch_bpf_obj(&obj_buf, &obj_buf_sz, + subtest, false); - if (ret == TEST_OK) { - ret = test__bpf_parsing(obj_buf, obj_buf_sz); - if (ret != TEST_OK) - pr_debug("Failed to parse test case '%s'\n", - bpf_source_table[i].desc); - } - free(obj_buf); - - switch (ret) { - case TEST_SKIP: - return TEST_SKIP; - case TEST_OK: - break; - default: - /* - * Test 0 is the basic LLVM test. If test 0 - * fail, the basic LLVM support not functional - * so the whole test should fail. If other test - * case fail, it can be fixed by adjusting - * config so don't report error. - */ - if (i == 0) - return TEST_FAIL; - else - return TEST_SKIP; + if (ret == TEST_OK) { + ret = test__bpf_parsing(obj_buf, obj_buf_sz); + if (ret != TEST_OK) { + pr_debug("Failed to parse test case '%s'\n", + bpf_source_table[subtest].desc); } } - return TEST_OK; + free(obj_buf); + + return ret; +} + +int test__llvm_subtest_get_nr(void) +{ + return __LLVM_TESTCASE_MAX; +} + +const char *test__llvm_subtest_get_desc(int subtest) +{ + if ((subtest < 0) || (subtest >= __LLVM_TESTCASE_MAX)) + return NULL; + + return bpf_source_table[subtest].desc; } diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index 204e4eeadea2..f92af527f080 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -1,6 +1,8 @@ #ifndef TESTS_H #define TESTS_H +#include + #define TEST_ASSERT_VAL(text, cond) \ do { \ if (!(cond)) { \ @@ -27,6 +29,11 @@ enum { struct test { const char *desc; int (*func)(int subtest); + struct { + bool skip_if_fail; + int (*get_nr)(void); + const char *(*get_desc)(int subtest); + } subtest; }; /* Tests */ @@ -66,6 +73,8 @@ int test__fdarray__add(int subtest); int test__kmod_path__parse(int subtest); int test__thread_map(int subtest); int test__llvm(int subtest); +const char *test__llvm_subtest_get_desc(int subtest); +int test__llvm_subtest_get_nr(void); int test__bpf(int subtest); int test_session_topology(int subtest); From 77a0cf682f7979554e10a6c605a1fef4f4197654 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Tue, 17 Nov 2015 08:32:49 +0000 Subject: [PATCH 18/37] perf test: Print result for each BPF subtest This patch prints each sub-tests results for BPF testcases. Before: # ./perf test BPF 37: Test BPF filter : Ok After: # ./perf test BPF 37: Test BPF filter : 37.1: Test basic BPF filtering : Ok 37.2: Test BPF prologue generation : Ok When a failure happens: # cat ~/.perfconfig [llvm] clang-path = "/bin/false" # ./perf test BPF 37: Test BPF filter : 37.1: Test basic BPF filtering : Skip 37.2: Test BPF prologue generation : Skip Suggested-and-Tested-by: Arnaldo Carvalho de Melo Signed-off-by: Wang Nan Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447749170-175898-5-git-send-email-wangnan0@huawei.com [ Fixed up not to use .func in an anonymous union ] Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/bpf.c | 40 ++++++++++++++++++++++++--------- tools/perf/tests/builtin-test.c | 5 +++++ tools/perf/tests/tests.h | 2 ++ 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/tools/perf/tests/bpf.c b/tools/perf/tests/bpf.c index 4efdc1607754..33689a0cf821 100644 --- a/tools/perf/tests/bpf.c +++ b/tools/perf/tests/bpf.c @@ -215,28 +215,46 @@ out: return ret; } -int test__bpf(int subtest __maybe_unused) +int test__bpf_subtest_get_nr(void) +{ + return (int)ARRAY_SIZE(bpf_testcase_table); +} + +const char *test__bpf_subtest_get_desc(int i) +{ + if (i < 0 || i >= (int)ARRAY_SIZE(bpf_testcase_table)) + return NULL; + return bpf_testcase_table[i].desc; +} + +int test__bpf(int i) { - unsigned int i; int err; + if (i < 0 || i >= (int)ARRAY_SIZE(bpf_testcase_table)) + return TEST_FAIL; + if (geteuid() != 0) { pr_debug("Only root can run BPF test\n"); return TEST_SKIP; } - for (i = 0; i < ARRAY_SIZE(bpf_testcase_table); i++) { - err = __test__bpf(i); - - if (err != TEST_OK) - return err; - } - - return TEST_OK; + err = __test__bpf(i); + return err; } #else -int test__bpf(void) +int test__bpf_subtest_get_nr(void) +{ + return 0; +} + +const char *test__bpf_subtest_get_desc(int i __maybe_unused) +{ + return NULL; +} + +int test__bpf(int i __maybe_unused) { pr_debug("Skip BPF test because BPF support is not compiled\n"); return TEST_SKIP; diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 813660976217..146ae9821c00 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -173,6 +173,11 @@ static struct test generic_tests[] = { { .desc = "Test BPF filter", .func = test__bpf, + .subtest = { + .skip_if_fail = true, + .get_nr = test__bpf_subtest_get_nr, + .get_desc = test__bpf_subtest_get_desc, + }, }, { .func = NULL, diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index f92af527f080..a0733aaad081 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -76,6 +76,8 @@ int test__llvm(int subtest); const char *test__llvm_subtest_get_desc(int subtest); int test__llvm_subtest_get_nr(void); int test__bpf(int subtest); +const char *test__bpf_subtest_get_desc(int subtest); +int test__bpf_subtest_get_nr(void); int test_session_topology(int subtest); #if defined(__arm__) || defined(__aarch64__) From 5bcf2fe05318deb6fec209b4028d8a31f9f47221 Mon Sep 17 00:00:00 2001 From: Wang Nan Date: Tue, 17 Nov 2015 08:32:50 +0000 Subject: [PATCH 19/37] perf test: Mute test cases error messages if verbose == 0 Sometimes error messages in breaks the pretty output of 'perf test'. For example: # mv /lib/modules/4.3.0-rc4+/build/vmlinux{,.bak} # perf test LLVM BPF 35: Test LLVM searching and compiling : 35.1: Basic BPF llvm compiling test : Ok 35.2: Test kbuild searching : Ok 35.3: Compile source for BPF prologue generation test : Ok 37: Test BPF filter : 37.1: Test basic BPF filtering : Ok 37.2: Test BPF prologue generation :Failed to find the path for kernel: No such file or directory FAILED! This patch mute test cases thoroughly by redirect their stdout and stderr to /dev/null when verbose == 0. After applying this patch: # ./perf test LLVM BPF 35: Test LLVM searching and compiling : 35.1: Basic BPF llvm compiling test : Ok 35.2: Test kbuild searching : Ok 35.3: Compile source for BPF prologue generation test : Ok 37: Test BPF filter : 37.1: Test basic BPF filtering : Ok 37.2: Test BPF prologue generation : FAILED! # ./perf test -v LLVM BPF 35: Test LLVM searching and compiling : 35.1: Basic BPF llvm compiling test : --- start --- test child forked, pid 13183 Kernel build dir is set to /lib/modules/4.3.0-rc4+/build set env: KBUILD_DIR=/lib/modules/4.3.0-rc4+/build ... bpf: config 'func=null_lseek file->f_mode offset orig' is ok Looking at the vmlinux_path (7 entries long) Failed to find the path for kernel: No such file or directory bpf_probe: failed to convert perf probe eventsFailed to add events selected by BPF test child finished with -1 ---- end ---- Test BPF filter subtest 1: FAILED! Signed-off-by: Wang Nan Tested-by: Arnaldo Carvalho de Melo Cc: Alexei Starovoitov Cc: Masami Hiramatsu Cc: Zefan Li Cc: pi3orama@163.com Link: http://lkml.kernel.org/r/1447749170-175898-6-git-send-email-wangnan0@huawei.com Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/builtin-test.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 146ae9821c00..2b1ade1aafc3 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -226,6 +226,18 @@ static int run_test(struct test *test, int subtest) if (!child) { pr_debug("test child forked, pid %d\n", getpid()); + if (!verbose) { + int nullfd = open("/dev/null", O_WRONLY); + if (nullfd >= 0) { + close(STDERR_FILENO); + close(STDOUT_FILENO); + + dup2(nullfd, STDOUT_FILENO); + dup2(STDOUT_FILENO, STDERR_FILENO); + close(nullfd); + } + } + err = test->func(subtest); exit(err); } From 05c8d802fa52ef17dbcce21c38b72b4a313eb036 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 18 Nov 2015 15:40:12 +0900 Subject: [PATCH 20/37] perf probe: Fix to free temporal Dwarf_Frame Since dwarf_cfi_addrframe returns malloc'd Dwarf_Frame object, it has to be freed after it is used. Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151118064011.30709.65674.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/probe-finder.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 05012bb178d7..1cab05a3831e 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -683,21 +683,24 @@ static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf) ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1); if (ret <= 0 || nops == 0) { pf->fb_ops = NULL; + ret = 0; #if _ELFUTILS_PREREQ(0, 142) } else if (nops == 1 && pf->fb_ops[0].atom == DW_OP_call_frame_cfa && pf->cfi != NULL) { - Dwarf_Frame *frame; + Dwarf_Frame *frame = NULL; if (dwarf_cfi_addrframe(pf->cfi, pf->addr, &frame) != 0 || dwarf_frame_cfa(frame, &pf->fb_ops, &nops) != 0) { pr_warning("Failed to get call frame on 0x%jx\n", (uintmax_t)pf->addr); - return -ENOENT; + ret = -ENOENT; } + free(frame); #endif } /* Call finder's callback handler */ - ret = pf->callback(sc_die, pf); + if (ret >= 0) + ret = pf->callback(sc_die, pf); /* *pf->fb_ops will be cached in libdw. Don't free it. */ pf->fb_ops = NULL; From 9afcb420d6cfeadf5d872f395061c611536615fb Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 18 Nov 2015 15:40:20 +0900 Subject: [PATCH 21/37] perf machine: Fix machine__findnew_module_map to put registered map Fix machine object to drop the reference to the map object after it inserted it into machine->kmaps. refcnt debugger shows what happened: ---- ==== [2] ==== Unreclaimed map: 0x346f750 Refcount +1 => 1 at ./perf(map__new2+0xb5) [0x4bdea5] ./perf() [0x4b8aaf] ./perf(modules__parse+0xfc) [0x4a9cbc] ./perf() [0x4b83c0] ./perf(machine__create_kernel_maps+0x148) [0x4bb208] ./perf(machine__new_host+0xfa) [0x4bb3fa] ./perf(init_probe_symbol_maps+0x93) [0x5062b3] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f5373899af5] ./perf() [0x4220a9] Refcount +1 => 2 at ./perf(maps__insert+0x9a) [0x4bfd4a] ./perf() [0x4b8acb] ./perf(modules__parse+0xfc) [0x4a9cbc] ./perf() [0x4b83c0] ./perf(machine__create_kernel_maps+0x148) [0x4bb208] ./perf(machine__new_host+0xfa) [0x4bb3fa] ./perf(init_probe_symbol_maps+0x93) [0x5062b3] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f5373899af5] ./perf() [0x4220a9] Refcount -1 => 1 at ./perf(map_groups__exit+0x94) [0x4bea54] ./perf(machine__delete+0x3d) [0x4b91ed] ./perf(exit_probe_symbol_maps+0x28) [0x506358] ./perf() [0x45628a] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f5373899af5] ./perf() [0x4220a9] ---- This pattern clearly shows that the refcnt of the map is acquired twice by map__new2 and maps__insert but released onlu once at map_groups__exit, when we purge its maps rbtree. Since maps__insert already reference counted the map, we have to drop the constructor (map__new2) reference count right after inserting it. These happened in machine__findnew_module_map, as below. ---- # eu-addr2line -e ./perf -f 0x4b8aaf machine__findnew_module_map inlined at util/machine.c:1046 in machine__create_module util/machine.c:582 # eu-addr2line -e ./perf -f 0x4b8acb map_groups__insert inlined at util/machine.c:585 in machine__create_module util/map.h:208 ---- (note that both are at util/machine.c:58X which is machine__findnew_module_map) Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151118064020.30709.40499.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/machine.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 8b303ff20289..0487d7795f13 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -585,6 +585,8 @@ struct map *machine__findnew_module_map(struct machine *machine, u64 start, map_groups__insert(&machine->kmaps, map); + /* Put the map here because map_groups__insert alread got it */ + map__put(map); out: free(m.name); return map; From e96e4078e9a5ea150b3ad9a296440a7976439e4a Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 18 Nov 2015 15:40:22 +0900 Subject: [PATCH 22/37] perf machine: Fix machine__destroy_kernel_maps to drop vmlinux_maps references Fix machine__destroy_kernel_maps() to drop vmlinux_maps references before filling it with NULL. Refcnt debugger shows ==== [1] ==== Unreclaimed map: 0x36b1070 Refcount +1 => 1 at ./perf(map__new2+0xb5) [0x4bdec5] ./perf(machine__create_kernel_maps+0x72) [0x4bb152] ./perf(machine__new_host+0xfa) [0x4bb41a] ./perf(init_probe_symbol_maps+0x93) [0x5062d3] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f1fc9fc4af5] ./perf() [0x4220a9] Refcount +1 => 2 at ./perf(maps__insert+0x9a) [0x4bfd6a] ./perf(machine__create_kernel_maps+0xc3) [0x4bb1a3] ./perf(machine__new_host+0xfa) [0x4bb41a] ./perf(init_probe_symbol_maps+0x93) [0x5062d3] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f1fc9fc4af5] ./perf() [0x4220a9] Refcount -1 => 1 at ./perf(map_groups__exit+0x94) [0x4bea74] ./perf(machine__delete+0x3d) [0x4b91fd] ./perf(exit_probe_symbol_maps+0x28) [0x506378] ./perf() [0x45628a] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f1fc9fc4af5] ./perf() [0x4220a9] map__new2() returns map with refcnt = 1, and also map_groups__insert gets it again in__machine__create_kernel_maps(). machine__destroy_kernel_maps() calls map_groups__remove() to decrement the refcnt, but before decrement it again (corresponding to map__new2), it makes vmlinux_maps[type] = NULL. And this may cause a refcnt leak. Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151118064022.30709.3897.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/machine.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 0487d7795f13..e9e09bee221c 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -790,6 +790,7 @@ void machine__destroy_kernel_maps(struct machine *machine) kmap->ref_reloc_sym = NULL; } + map__put(machine->vmlinux_maps[type]); machine->vmlinux_maps[type] = NULL; } } From ebe9729c8c3171aa46ad5d7af40acdc29806689d Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 18 Nov 2015 15:40:24 +0900 Subject: [PATCH 23/37] perf machine: Fix to destroy kernel maps when machine exits Actually machine__exit forgot to call machine__destroy_kernel_maps. This fixes some memory leaks on map as below. Without this fix. ---- ./perf probe vfs_read Added new event: probe:vfs_read (on vfs_read) You can now use it in all perf tools, such as: perf record -e probe:vfs_read -aR sleep 1 REFCNT: BUG: Unreclaimed objects found. REFCNT: Total 4 objects are not reclaimed. To see all backtraces, rerun with -v option ---- With this fix. ---- ./perf probe vfs_read Added new event: probe:vfs_read (on vfs_read) You can now use it in all perf tools, such as: perf record -e probe:vfs_read -aR sleep 1 REFCNT: BUG: Unreclaimed objects found. REFCNT: Total 2 objects are not reclaimed. To see all backtraces, rerun with -v option ---- Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151118064024.30709.43577.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/machine.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index e9e09bee221c..a358771fe9e3 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -122,6 +122,7 @@ void machine__delete_threads(struct machine *machine) void machine__exit(struct machine *machine) { + machine__destroy_kernel_maps(machine); map_groups__exit(&machine->kmaps); dsos__exit(&machine->dsos); machine__exit_vdso(machine); From c4068f51d40df151a661a384ab1309b11d7f012e Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Thu, 19 Nov 2015 15:04:53 +0900 Subject: [PATCH 24/37] perf tools: Make perf_exec_path() always return malloc'd string Since system_path() returns malloc'd string if given path is not an absolute path, perf_exec_path() sometimes returns a static string and sometimes returns a malloc'd string depending on the environment variables or command options. This may cause a memory leak because the caller can not unconditionally free the returned string. This fixes perf_exec_path() and system_path() to always return a malloc'd string, so the caller can always free it. Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151119060453.14210.65666.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/exec_cmd.c | 21 +++++++++++---------- tools/perf/util/exec_cmd.h | 5 +++-- tools/perf/util/help.c | 6 ++++-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tools/perf/util/exec_cmd.c b/tools/perf/util/exec_cmd.c index 7adf4ad15d8f..1099e92f5ee1 100644 --- a/tools/perf/util/exec_cmd.c +++ b/tools/perf/util/exec_cmd.c @@ -9,17 +9,17 @@ static const char *argv_exec_path; static const char *argv0_path; -const char *system_path(const char *path) +char *system_path(const char *path) { static const char *prefix = PREFIX; struct strbuf d = STRBUF_INIT; if (is_absolute_path(path)) - return path; + return strdup(path); strbuf_addf(&d, "%s/%s", prefix, path); path = strbuf_detach(&d, NULL); - return path; + return (char *)path; } const char *perf_extract_argv0_path(const char *argv0) @@ -52,17 +52,16 @@ void perf_set_argv_exec_path(const char *exec_path) /* Returns the highest-priority, location to look for perf programs. */ -const char *perf_exec_path(void) +char *perf_exec_path(void) { - const char *env; + char *env; if (argv_exec_path) - return argv_exec_path; + return strdup(argv_exec_path); env = getenv(EXEC_PATH_ENVIRONMENT); - if (env && *env) { - return env; - } + if (env && *env) + return strdup(env); return system_path(PERF_EXEC_PATH); } @@ -83,9 +82,11 @@ void setup_path(void) { const char *old_path = getenv("PATH"); struct strbuf new_path = STRBUF_INIT; + char *tmp = perf_exec_path(); - add_path(&new_path, perf_exec_path()); + add_path(&new_path, tmp); add_path(&new_path, argv0_path); + free(tmp); if (old_path) strbuf_addstr(&new_path, old_path); diff --git a/tools/perf/util/exec_cmd.h b/tools/perf/util/exec_cmd.h index bc4b915963f5..48b4175f1e11 100644 --- a/tools/perf/util/exec_cmd.h +++ b/tools/perf/util/exec_cmd.h @@ -3,10 +3,11 @@ extern void perf_set_argv_exec_path(const char *exec_path); extern const char *perf_extract_argv0_path(const char *path); -extern const char *perf_exec_path(void); extern void setup_path(void); extern int execv_perf_cmd(const char **argv); /* NULL terminated */ extern int execl_perf_cmd(const char *cmd, ...); -extern const char *system_path(const char *path); +/* perf_exec_path and system_path return malloc'd string, caller must free it */ +extern char *perf_exec_path(void); +extern char *system_path(const char *path); #endif /* __PERF_EXEC_CMD_H */ diff --git a/tools/perf/util/help.c b/tools/perf/util/help.c index 86c37c472263..fa1fc4acb8a4 100644 --- a/tools/perf/util/help.c +++ b/tools/perf/util/help.c @@ -159,7 +159,7 @@ void load_command_list(const char *prefix, struct cmdnames *other_cmds) { const char *env_path = getenv("PATH"); - const char *exec_path = perf_exec_path(); + char *exec_path = perf_exec_path(); if (exec_path) { list_commands_in_dir(main_cmds, exec_path, prefix); @@ -187,6 +187,7 @@ void load_command_list(const char *prefix, sizeof(*other_cmds->names), cmdname_compare); uniq(other_cmds); } + free(exec_path); exclude_cmds(other_cmds, main_cmds); } @@ -203,13 +204,14 @@ void list_commands(const char *title, struct cmdnames *main_cmds, longest = other_cmds->names[i]->len; if (main_cmds->cnt) { - const char *exec_path = perf_exec_path(); + char *exec_path = perf_exec_path(); printf("available %s in '%s'\n", title, exec_path); printf("----------------"); mput_char('-', strlen(title) + strlen(exec_path)); putchar('\n'); pretty_print_string_list(main_cmds, longest); putchar('\n'); + free(exec_path); } if (other_cmds->cnt) { From 8d5c340dfcd48751fdff301bb2a7e3f875652dcb Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 18 Nov 2015 15:40:27 +0900 Subject: [PATCH 25/37] perf tools: Fix to put new map after inserting to map_groups in dso__load_sym Fix dso__load_sym to put the map object which is already insterted to kmaps. Refcnt debugger shows ==== [0] ==== Unreclaimed map: 0x39113e0 Refcount +1 => 1 at ./perf(map__new2+0xb5) [0x4be155] ./perf(dso__load_sym+0xee1) [0x503461] ./perf(dso__load_vmlinux+0xbf) [0x4aa6df] ./perf(dso__load_vmlinux_path+0x8c) [0x4aa83c] ./perf() [0x50528a] ./perf(convert_perf_probe_events+0xd79) [0x50ac29] ./perf() [0x45600f] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f152368baf5] ./perf() [0x4220a9] Refcount +1 => 2 at ./perf(maps__insert+0x9a) [0x4bfffa] ./perf(dso__load_sym+0xf89) [0x503509] ./perf(dso__load_vmlinux+0xbf) [0x4aa6df] ./perf(dso__load_vmlinux_path+0x8c) [0x4aa83c] ./perf() [0x50528a] ./perf(convert_perf_probe_events+0xd79) [0x50ac29] ./perf() [0x45600f] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f152368baf5] ./perf() [0x4220a9] Refcount -1 => 1 at ./perf(map_groups__exit+0x94) [0x4bed04] ./perf(machine__delete+0xb0) [0x4b9300] ./perf(exit_probe_symbol_maps+0x28) [0x506608] ./perf() [0x45628a] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f152368baf5] ./perf() [0x4220a9] This means that the dso__load_sym calls map__new2 and maps_insert, both of them bump the map refcount, but map_groups__exit will drop just one reference. Fix it by dropping the refcount after inserting it into kmaps. Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151118064026.30709.50038.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/symbol-elf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 475d88d0a1c9..53f19968bfa2 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -1042,6 +1042,8 @@ int dso__load_sym(struct dso *dso, struct map *map, } curr_dso->symtab_type = dso->symtab_type; map_groups__insert(kmaps, curr_map); + /* kmaps already got it */ + map__put(curr_map); dsos__add(&map->groups->machine->dsos, curr_dso); dso__set_loaded(curr_dso, map->type); } else From 82de26abdc127172fd7453a61d35a9b33bf4f871 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 18 Nov 2015 15:40:31 +0900 Subject: [PATCH 26/37] perf tools: Fix __dsos__addnew to put dso after adding it to the list __dsos__addnew should drop the constructor reference to dso after adding it to the list, because __dsos__add() will get a reference that will be kept while it is in the list. This fixes DSO leaks when entries are removed to the list and the refcount never gets to zero. Refcnt debugger shows: ==== [0] ==== Unreclaimed dso: 0x2fccab0 Refcount +1 => 1 at ./perf(dso__new+0x1ff) [0x4a62df] ./perf(__dsos__addnew+0x29) [0x4a6e19] ./perf(dsos__findnew+0xd1) [0x4a7281] ./perf(machine__findnew_kernel+0x27) [0x4a5e17] ./perf() [0x4b8df2] ./perf(machine__create_kernel_maps+0x28) [0x4bb528] ./perf(machine__new_host+0xfa) [0x4bb84a] ./perf(init_probe_symbol_maps+0x93) [0x506713] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f46df132af5] ./perf() [0x4220a9] Refcount +1 => 2 at ./perf(__dsos__addnew+0xfb) [0x4a6eeb] ./perf(dsos__findnew+0xd1) [0x4a7281] ./perf(machine__findnew_kernel+0x27) [0x4a5e17] ./perf() [0x4b8df2] ./perf(machine__create_kernel_maps+0x28) [0x4bb528] ./perf(machine__new_host+0xfa) [0x4bb84a] ./perf(init_probe_symbol_maps+0x93) [0x506713] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f46df132af5] ./perf() [0x4220a9] Refcount +1 => 3 at ./perf(dsos__findnew+0x7e) [0x4a722e] ./perf(machine__findnew_kernel+0x27) [0x4a5e17] ./perf() [0x4b8df2] ./perf(machine__create_kernel_maps+0x28) [0x4bb528] ./perf(machine__new_host+0xfa) [0x4bb84a] ./perf(init_probe_symbol_maps+0x93) [0x506713] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f46df132af5] ./perf() [0x4220a9] [snip] Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151118064031.30709.81460.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/dso.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index 425df5c86c9c..e8e9a9dbf5e3 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -1243,6 +1243,8 @@ struct dso *__dsos__addnew(struct dsos *dsos, const char *name) if (dso != NULL) { __dsos__add(dsos, dso); dso__set_basename(dso); + /* Put dso here because __dsos_add already got it */ + dso__put(dso); } return dso; } From 1154c957607afdf5936ae14e1be27d7ca4e7bd30 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 18 Nov 2015 15:40:33 +0900 Subject: [PATCH 27/37] perf tools: Fix machine__create_kernel_maps to put kernel dso refcount Fix machine__create_kernel_maps() to put kernel dso because the dso has been gotten via __machine__create_kernel_maps(). Refcnt debugger shows: ==== [0] ==== Unreclaimed dso: 0x3036ab0 Refcount +1 => 1 at ./perf(dso__new+0x1ff) [0x4a62df] ./perf(__dsos__addnew+0x29) [0x4a6e19] ./perf(dsos__findnew+0xd1) [0x4a7181] ./perf(machine__findnew_kernel+0x27) [0x4a5e17] ./perf() [0x4b8cf2] ./perf(machine__create_kernel_maps+0x28) [0x4bb428] ./perf(machine__new_host+0xfa) [0x4bb74a] ./perf(init_probe_symbol_maps+0x93) [0x506613] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7ffa6809eaf5] ./perf() [0x4220a9] [snip] Refcount +1 => 2 at ./perf(dsos__findnew+0x7e) [0x4a712e] ./perf(machine__findnew_kernel+0x27) [0x4a5e17] ./perf() [0x4b8cf2] ./perf(machine__create_kernel_maps+0x28) [0x4bb428] ./perf(machine__new_host+0xfa) [0x4bb74a] ./perf(init_probe_symbol_maps+0x93) [0x506613] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7ffa6809eaf5] ./perf() [0x4220a9] [snip] Refcount -1 => 1 at ./perf(dso__put+0x2f) [0x4a664f] ./perf(machine__delete+0xfe) [0x4b93ee] ./perf(exit_probe_symbol_maps+0x28) [0x5066b8] ./perf() [0x45628a] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7ffa6809eaf5] ./perf() [0x4220a9] Actually, dsos__findnew gets the dso before returning it, so the dso user (in this case machine__create_kernel_maps) has to put the dso after used. Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151118064033.30709.98954.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/machine.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index a358771fe9e3..0b4a05c14204 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -1088,11 +1088,14 @@ int machine__create_kernel_maps(struct machine *machine) struct dso *kernel = machine__get_kernel(machine); const char *name; u64 addr = machine__get_running_kernel_start(machine, &name); - if (!addr) + int ret; + + if (!addr || kernel == NULL) return -1; - if (kernel == NULL || - __machine__create_kernel_maps(machine, kernel) < 0) + ret = __machine__create_kernel_maps(machine, kernel); + dso__put(kernel); + if (ret < 0) return -1; if (symbol_conf.use_modules && machine__create_modules(machine) < 0) { From 566c69c36e6178774dd484ea4a02b76f6bd0ede4 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Wed, 18 Nov 2015 15:40:35 +0900 Subject: [PATCH 28/37] perf machine: Fix machine__findnew_module_map to put dso Fix machine__findnew_module_map to drop the reference to the dso because it is already referenced by both machine__findnew_module_dso() and map__new2(). Refcnt debugger shows: ==== [1] ==== Unreclaimed dso: 0x1ffd980 Refcount +1 => 1 at ./perf(dso__new+0x1ff) [0x4a62df] ./perf(__dsos__addnew+0x29) [0x4a6e19] ./perf() [0x4b8b91] ./perf(modules__parse+0xfc) [0x4a9d5c] ./perf() [0x4b8460] ./perf(machine__create_kernel_maps+0x150) [0x4bb550] ./perf(machine__new_host+0xfa) [0x4bb75a] ./perf(init_probe_symbol_maps+0x93) [0x506623] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f1345a8eaf5] ./perf() [0x4220a9] This map_groups__insert(0x4b8b91) already gets a reference to the new dso: ---- eu-addr2line -e ./perf -f 0x4b8b91 map_groups__insert inlined at util/machine.c:586 in machine__create_module util/map.h:207 ---- So this dso refcnt will be released when map_groups gets released. [snip] Refcount +1 => 2 at ./perf(dso__get+0x34) [0x4a65f4] ./perf() [0x4b8b35] ./perf(modules__parse+0xfc) [0x4a9d5c] ./perf() [0x4b8460] ./perf(machine__create_kernel_maps+0x150) [0x4bb550] ./perf(machine__new_host+0xfa) [0x4bb75a] ./perf(init_probe_symbol_maps+0x93) [0x506623] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f1345a8eaf5] ./perf() [0x4220a9] Here, machine__findnew_module_dso(0x4b8b35) gets the dso (and stores it in a local variable): ---- # eu-addr2line -e ./perf -f 0x4b8b35 machine__findnew_module_dso inlined at util/machine.c:578 in machine__create_module util/machine.c:514 ---- Refcount +1 => 3 at ./perf(dso__get+0x34) [0x4a65f4] ./perf(map__new2+0x76) [0x4be1c6] ./perf() [0x4b8b4f] ./perf(modules__parse+0xfc) [0x4a9d5c] ./perf() [0x4b8460] ./perf(machine__create_kernel_maps+0x150) [0x4bb550] ./perf(machine__new_host+0xfa) [0x4bb75a] ./perf(init_probe_symbol_maps+0x93) [0x506623] ./perf() [0x455ffa] ./perf(cmd_probe+0x6c) [0x4566bc] ./perf() [0x47abc5] ./perf(main+0x610) [0x421f90] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f1345a8eaf5] ./perf() [0x4220a9] But also map__new2() gets the dso which will be put when the map is released. So, we have to drop the constructor reference obtained in machine__findnew_module_dso(). Signed-off-by: Masami Hiramatsu Cc: Adrian Hunter Cc: Jiri Olsa Cc: Namhyung Kim Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/20151118064035.30709.58824.stgit@localhost.localdomain Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/machine.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 0b4a05c14204..7f5071a4d9aa 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -565,7 +565,7 @@ struct map *machine__findnew_module_map(struct machine *machine, u64 start, const char *filename) { struct map *map = NULL; - struct dso *dso; + struct dso *dso = NULL; struct kmod_path m; if (kmod_path__parse_name(&m, filename)) @@ -589,6 +589,8 @@ struct map *machine__findnew_module_map(struct machine *machine, u64 start, /* Put the map here because map_groups__insert alread got it */ map__put(map); out: + /* put the dso here, corresponding to machine__findnew_module_dso */ + dso__put(dso); free(m.name); return map; } From 26e779245dd6f5270c0696860438e5c03d0780fd Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:37 +0900 Subject: [PATCH 29/37] perf report: Support folded callchain mode on --stdio Add new call chain option (-g) 'folded' to print callchains in a line. The callchains are separated by semicolons, and preceded by (absolute) percent values and a space. For example, the following 20 lines can be printed in 3 lines with the folded output mode: $ perf report -g flat --no-children | grep -v ^# | head -20 60.48% swapper [kernel.vmlinux] [k] intel_idle 54.60% intel_idle cpuidle_enter_state cpuidle_enter call_cpuidle cpu_startup_entry start_secondary 5.88% intel_idle cpuidle_enter_state cpuidle_enter call_cpuidle cpu_startup_entry rest_init start_kernel x86_64_start_reservations x86_64_start_kernel $ perf report -g folded --no-children | grep -v ^# | head -3 60.48% swapper [kernel.vmlinux] [k] intel_idle 54.60% intel_idle;cpuidle_enter_state;cpuidle_enter;call_cpuidle;cpu_startup_entry;start_secondary 5.88% intel_idle;cpuidle_enter_state;cpuidle_enter;call_cpuidle;cpu_startup_entry;rest_init;start_kernel;x86_64_start_reservations;x86_64_start_kernel This mode is supported only for --stdio now and intended to be used by some scripts like in FlameGraphs[1]. Support for other UI might be added later. [1] http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html Requested-and-Tested-by: Brendan Gregg Signed-off-by: Namhyung Kim Tested-by: Arnaldo Carvalho de Melo Acked-by: Jiri Olsa Cc: Andi Kleen Cc: David Ahern Cc: Frederic Weisbecker Cc: Kan Liang Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-2-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Documentation/perf-report.txt | 1 + tools/perf/ui/stdio/hist.c | 55 ++++++++++++++++++++++++ tools/perf/util/callchain.c | 6 +++ tools/perf/util/callchain.h | 5 ++- 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 5ce8da1e1256..f7d81aac9188 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -181,6 +181,7 @@ OPTIONS - graph: use a graph tree, displaying absolute overhead rates. (default) - fractal: like graph, but displays relative rates. Each branch of the tree is considered as a new profiled object. + - folded: call chains are displayed in a line, separated by semicolons - none: disable call chain display. threshold is a percentage value which specifies a minimum percent to be diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index dfcbc90146ef..ea7984932d9a 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -260,6 +260,58 @@ static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *tree, return ret; } +static size_t __callchain__fprintf_folded(FILE *fp, struct callchain_node *node) +{ + const char *sep = symbol_conf.field_sep ?: ";"; + struct callchain_list *chain; + size_t ret = 0; + char bf[1024]; + bool first; + + if (!node) + return 0; + + ret += __callchain__fprintf_folded(fp, node->parent); + + first = (ret == 0); + list_for_each_entry(chain, &node->val, list) { + if (chain->ip >= PERF_CONTEXT_MAX) + continue; + ret += fprintf(fp, "%s%s", first ? "" : sep, + callchain_list__sym_name(chain, + bf, sizeof(bf), false)); + first = false; + } + + return ret; +} + +static size_t callchain__fprintf_folded(FILE *fp, struct rb_root *tree, + u64 total_samples) +{ + size_t ret = 0; + u32 entries_printed = 0; + struct callchain_node *chain; + struct rb_node *rb_node = rb_first(tree); + + while (rb_node) { + double percent; + + chain = rb_entry(rb_node, struct callchain_node, rb_node); + percent = chain->hit * 100.0 / total_samples; + + ret += fprintf(fp, "%.2f%% ", percent); + ret += __callchain__fprintf_folded(fp, chain); + ret += fprintf(fp, "\n"); + if (++entries_printed == callchain_param.print_limit) + break; + + rb_node = rb_next(rb_node); + } + + return ret; +} + static size_t hist_entry_callchain__fprintf(struct hist_entry *he, u64 total_samples, int left_margin, FILE *fp) @@ -278,6 +330,9 @@ static size_t hist_entry_callchain__fprintf(struct hist_entry *he, case CHAIN_FLAT: return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples); break; + case CHAIN_FOLDED: + return callchain__fprintf_folded(fp, &he->sorted_chain, total_samples); + break; case CHAIN_NONE: break; default: diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 735ad48e1858..08cb220ba5ea 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -44,6 +44,10 @@ static int parse_callchain_mode(const char *value) callchain_param.mode = CHAIN_GRAPH_REL; return 0; } + if (!strncmp(value, "folded", strlen(value))) { + callchain_param.mode = CHAIN_FOLDED; + return 0; + } return -1; } @@ -218,6 +222,7 @@ rb_insert_callchain(struct rb_root *root, struct callchain_node *chain, switch (mode) { case CHAIN_FLAT: + case CHAIN_FOLDED: if (rnode->hit < chain->hit) p = &(*p)->rb_left; else @@ -338,6 +343,7 @@ int callchain_register_param(struct callchain_param *param) param->sort = sort_chain_graph_rel; break; case CHAIN_FLAT: + case CHAIN_FOLDED: param->sort = sort_chain_flat; break; case CHAIN_NONE: diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index fce8161e54db..544d99ac169c 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -24,7 +24,7 @@ #define CALLCHAIN_RECORD_HELP CALLCHAIN_HELP RECORD_MODE_HELP RECORD_SIZE_HELP #define CALLCHAIN_REPORT_HELP \ - HELP_PAD "print_type:\tcall graph printing style (graph|flat|fractal|none)\n" \ + HELP_PAD "print_type:\tcall graph printing style (graph|flat|fractal|folded|none)\n" \ HELP_PAD "threshold:\tminimum call graph inclusion threshold ()\n" \ HELP_PAD "print_limit:\tmaximum number of call graph entry ()\n" \ HELP_PAD "order:\t\tcall graph order (caller|callee)\n" \ @@ -43,7 +43,8 @@ enum chain_mode { CHAIN_NONE, CHAIN_FLAT, CHAIN_GRAPH_ABS, - CHAIN_GRAPH_REL + CHAIN_GRAPH_REL, + CHAIN_FOLDED, }; enum chain_order { From 5ab250cafcd884a2638b102239870bddca42ff88 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:39 +0900 Subject: [PATCH 30/37] perf callchain: Abstract callchain print function This is a preparation to support for printing other type of callchain value like count or period. Signed-off-by: Namhyung Kim Tested-by: Brendan Gregg Cc: Andi Kleen Cc: David Ahern Cc: Frederic Weisbecker Cc: Jiri Olsa Cc: Kan Liang Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-4-git-send-email-namhyung@kernel.org [ renamed new _sprintf_ operation to _scnprintf_ ] Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/ui/browsers/hists.c | 8 +++++--- tools/perf/ui/gtk/hists.c | 8 ++------ tools/perf/ui/stdio/hist.c | 35 +++++++++++++++++----------------- tools/perf/util/callchain.c | 29 ++++++++++++++++++++++++++++ tools/perf/util/callchain.h | 4 ++++ 5 files changed, 57 insertions(+), 27 deletions(-) diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index fa9eb92c9e24..0b18857a36e8 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -592,7 +592,6 @@ static int hist_browser__show_callchain(struct hist_browser *browser, while (node) { struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); struct rb_node *next = rb_next(node); - u64 cumul = callchain_cumul_hits(child); struct callchain_list *chain; char folded_sign = ' '; int first = true; @@ -619,9 +618,12 @@ static int hist_browser__show_callchain(struct hist_browser *browser, browser->show_dso); if (was_first && need_percent) { - double percent = cumul * 100.0 / total; + char buf[64]; - if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) + callchain_node__scnprintf_value(child, buf, sizeof(buf), + total); + + if (asprintf(&alloc_str, "%s %s", buf, str) < 0) str = "Not enough memory!"; else str = alloc_str; diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index 4b3585eed1e8..cff7bb9d9632 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -100,14 +100,10 @@ static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, struct callchain_list *chain; GtkTreeIter iter, new_parent; bool need_new_parent; - double percent; - u64 hits, child_total; + u64 child_total; node = rb_entry(nd, struct callchain_node, rb_node); - hits = callchain_cumul_hits(node); - percent = 100.0 * hits / total; - new_parent = *parent; need_new_parent = !has_single_node && (node->val_nr > 1); @@ -116,7 +112,7 @@ static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, gtk_tree_store_append(store, &iter, &new_parent); - scnprintf(buf, sizeof(buf), "%5.2f%%", percent); + callchain_node__scnprintf_value(node, buf, sizeof(buf), total); gtk_tree_store_set(store, &iter, 0, buf, -1); callchain_list__sym_name(chain, buf, sizeof(buf), false); diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index ea7984932d9a..f4de055cab9b 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -34,10 +34,10 @@ static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, return ret; } -static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, +static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_node *node, + struct callchain_list *chain, int depth, int depth_mask, int period, - u64 total_samples, u64 hits, - int left_margin) + u64 total_samples, int left_margin) { int i; size_t ret = 0; @@ -50,10 +50,9 @@ static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, else ret += fprintf(fp, " "); if (!period && i == depth - 1) { - double percent; - - percent = hits * 100.0 / total_samples; - ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent); + ret += fprintf(fp, "--"); + ret += callchain_node__fprintf_value(node, fp, total_samples); + ret += fprintf(fp, "--"); } else ret += fprintf(fp, "%s", " "); } @@ -120,10 +119,9 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, left_margin); i = 0; list_for_each_entry(chain, &child->val, list) { - ret += ipchain__fprintf_graph(fp, chain, depth, + ret += ipchain__fprintf_graph(fp, child, chain, depth, new_depth_mask, i++, total_samples, - cumul, left_margin); } @@ -143,14 +141,17 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, if (callchain_param.mode == CHAIN_GRAPH_REL && remaining && remaining != total_samples) { + struct callchain_node rem_node = { + .hit = remaining, + }; if (!rem_sq_bracket) return ret; new_depth_mask &= ~(1 << (depth - 1)); - ret += ipchain__fprintf_graph(fp, &rem_hits, depth, + ret += ipchain__fprintf_graph(fp, &rem_node, &rem_hits, depth, new_depth_mask, 0, total_samples, - remaining, left_margin); + left_margin); } return ret; @@ -243,12 +244,11 @@ static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *tree, struct rb_node *rb_node = rb_first(tree); while (rb_node) { - double percent; - chain = rb_entry(rb_node, struct callchain_node, rb_node); - percent = chain->hit * 100.0 / total_samples; - ret = percent_color_fprintf(fp, " %6.2f%%\n", percent); + ret += fprintf(fp, " "); + ret += callchain_node__fprintf_value(chain, fp, total_samples); + ret += fprintf(fp, "\n"); ret += __callchain__fprintf_flat(fp, chain, total_samples); ret += fprintf(fp, "\n"); if (++entries_printed == callchain_param.print_limit) @@ -295,12 +295,11 @@ static size_t callchain__fprintf_folded(FILE *fp, struct rb_root *tree, struct rb_node *rb_node = rb_first(tree); while (rb_node) { - double percent; chain = rb_entry(rb_node, struct callchain_node, rb_node); - percent = chain->hit * 100.0 / total_samples; - ret += fprintf(fp, "%.2f%% ", percent); + ret += callchain_node__fprintf_value(chain, fp, total_samples); + ret += fprintf(fp, " "); ret += __callchain__fprintf_folded(fp, chain); ret += fprintf(fp, "\n"); if (++entries_printed == callchain_param.print_limit) diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 08cb220ba5ea..b948bd068966 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -805,6 +805,35 @@ char *callchain_list__sym_name(struct callchain_list *cl, return bf; } +char *callchain_node__scnprintf_value(struct callchain_node *node, + char *bf, size_t bfsize, u64 total) +{ + double percent = 0.0; + u64 period = callchain_cumul_hits(node); + + if (callchain_param.mode == CHAIN_FOLDED) + period = node->hit; + if (total) + percent = period * 100.0 / total; + + scnprintf(bf, bfsize, "%.2f%%", percent); + return bf; +} + +int callchain_node__fprintf_value(struct callchain_node *node, + FILE *fp, u64 total) +{ + double percent = 0.0; + u64 period = callchain_cumul_hits(node); + + if (callchain_param.mode == CHAIN_FOLDED) + period = node->hit; + if (total) + percent = period * 100.0 / total; + + return percent_color_fprintf(fp, "%.2f%%", percent); +} + static void free_callchain_node(struct callchain_node *node) { struct callchain_list *list, *tmp; diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index 544d99ac169c..060e636e33ab 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -230,6 +230,10 @@ static inline int arch_skip_callchain_idx(struct thread *thread __maybe_unused, char *callchain_list__sym_name(struct callchain_list *cl, char *bf, size_t bfsize, bool show_dso); +char *callchain_node__scnprintf_value(struct callchain_node *node, + char *bf, size_t bfsize, u64 total); +int callchain_node__fprintf_value(struct callchain_node *node, + FILE *fp, u64 total); void free_callchain(struct callchain_root *root); From 5e47f8ff406296bd078716d71283796ca5c6544b Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:40 +0900 Subject: [PATCH 31/37] perf callchain: Add count fields to struct callchain_node It's to track the count of occurrences of the callchains. Signed-off-by: Namhyung Kim Acked-by: Brendan Gregg Acked-by: Jiri Olsa Cc: Andi Kleen Cc: David Ahern Cc: Frederic Weisbecker Cc: Kan Liang Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-5-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/callchain.c | 10 ++++++++++ tools/perf/util/callchain.h | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index b948bd068966..e390edd31504 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -437,6 +437,8 @@ add_child(struct callchain_node *parent, new->children_hit = 0; new->hit = period; + new->children_count = 0; + new->count = 1; return new; } @@ -484,6 +486,9 @@ split_add_child(struct callchain_node *parent, parent->children_hit = callchain_cumul_hits(new); new->val_nr = parent->val_nr - idx_local; parent->val_nr = idx_local; + new->count = parent->count; + new->children_count = parent->children_count; + parent->children_count = callchain_cumul_counts(new); /* create a new child for the new branch if any */ if (idx_total < cursor->nr) { @@ -494,6 +499,8 @@ split_add_child(struct callchain_node *parent, parent->hit = 0; parent->children_hit += period; + parent->count = 0; + parent->children_count += 1; node = callchain_cursor_current(cursor); new = add_child(parent, cursor, period); @@ -516,6 +523,7 @@ split_add_child(struct callchain_node *parent, rb_insert_color(&new->rb_node_in, &parent->rb_root_in); } else { parent->hit = period; + parent->count = 1; } } @@ -562,6 +570,7 @@ append_chain_children(struct callchain_node *root, inc_children_hit: root->children_hit += period; + root->children_count++; } static int @@ -614,6 +623,7 @@ append_chain(struct callchain_node *root, /* we match 100% of the path, increment the hit */ if (matches == root->val_nr && cursor->pos == cursor->nr) { root->hit += period; + root->count++; return 0; } diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index 060e636e33ab..cdb386d9ba02 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -60,6 +60,8 @@ struct callchain_node { struct rb_root rb_root_in; /* input tree of children */ struct rb_root rb_root; /* sorted output tree of children */ unsigned int val_nr; + unsigned int count; + unsigned int children_count; u64 hit; u64 children_hit; }; @@ -145,6 +147,11 @@ static inline u64 callchain_cumul_hits(struct callchain_node *node) return node->hit + node->children_hit; } +static inline unsigned callchain_cumul_counts(struct callchain_node *node) +{ + return node->count + node->children_count; +} + int callchain_register_param(struct callchain_param *param); int callchain_append(struct callchain_root *root, struct callchain_cursor *cursor, From f2af008695e0b54a58b76caecd52af7e6c97fb29 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:41 +0900 Subject: [PATCH 32/37] perf report: Add callchain value option Now -g/--call-graph option supports how to display callchain values. Possible values are 'percent', 'period' and 'count'. The percent is same as before and it's the default behavior. The period displays the raw period value rather than the percentage. The count displays the number of occurrences. $ perf report --no-children --stdio -g percent ... 39.93% swapper [kernel.vmlinux] [k] intel_idel | ---intel_idle cpuidle_enter_state cpuidle_enter call_cpuidle cpu_startup_entry | |--28.63%-- start_secondary | --11.30%-- rest_init $ perf report --no-children --show-total-period --stdio -g period ... 39.93% 13018705 swapper [kernel.vmlinux] [k] intel_idel | ---intel_idle cpuidle_enter_state cpuidle_enter call_cpuidle cpu_startup_entry | |--9334403-- start_secondary | --3684302-- rest_init $ perf report --no-children --show-nr-samples --stdio -g count ... 39.93% 80 swapper [kernel.vmlinux] [k] intel_idel | ---intel_idle cpuidle_enter_state cpuidle_enter call_cpuidle cpu_startup_entry | |--57-- start_secondary | --23-- rest_init Signed-off-by: Namhyung Kim Acked-by: Brendan Gregg Cc: Andi Kleen Cc: David Ahern Cc: Frederic Weisbecker Cc: Jiri Olsa Cc: Kan Liang Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-6-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Documentation/perf-report.txt | 13 +++-- tools/perf/builtin-report.c | 4 +- tools/perf/ui/stdio/hist.c | 10 +++- tools/perf/util/callchain.c | 62 ++++++++++++++++++++---- tools/perf/util/callchain.h | 10 +++- tools/perf/util/util.c | 3 +- 6 files changed, 84 insertions(+), 18 deletions(-) diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index f7d81aac9188..dab99ed2b339 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -170,11 +170,11 @@ OPTIONS Dump raw trace in ASCII. -g:: ---call-graph=:: +--call-graph=:: Display call chains using type, min percent threshold, print limit, - call order, sort key and branch. Note that ordering of parameters is not - fixed so any parement can be given in an arbitraty order. One exception - is the print_limit which should be preceded by threshold. + call order, sort key, optional branch and value. Note that ordering of + parameters is not fixed so any parement can be given in an arbitraty order. + One exception is the print_limit which should be preceded by threshold. print_type can be either: - flat: single column, linear exposure of call chains. @@ -205,6 +205,11 @@ OPTIONS - branch: include last branch information in callgraph when available. Usually more convenient to use --branch-history for this. + value can be: + - percent: diplay overhead percent (default) + - period: display event period + - count: display event count + --children:: Accumulate callchain of children to parent entry so that then can show up in the output. The output will have a new "Children" column diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index f256fac1e722..14428342b47b 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -625,7 +625,7 @@ parse_percent_limit(const struct option *opt, const char *str, return 0; } -#define CALLCHAIN_DEFAULT_OPT "graph,0.5,caller,function" +#define CALLCHAIN_DEFAULT_OPT "graph,0.5,caller,function,percent" const char report_callchain_help[] = "Display call graph (stack chain/backtrace):\n\n" CALLCHAIN_REPORT_HELP @@ -708,7 +708,7 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) OPT_BOOLEAN('x', "exclude-other", &symbol_conf.exclude_other, "Only display entries with parent-match"), OPT_CALLBACK_DEFAULT('g', "call-graph", &report, - "print_type,threshold[,print_limit],order,sort_key[,branch]", + "print_type,threshold[,print_limit],order,sort_key[,branch],value", report_callchain_help, &report_parse_callchain_opt, callchain_default_opt), OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain, diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index f4de055cab9b..7ebc661be267 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -81,13 +81,14 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, int depth_mask, int left_margin) { struct rb_node *node, *next; - struct callchain_node *child; + struct callchain_node *child = NULL; struct callchain_list *chain; int new_depth_mask = depth_mask; u64 remaining; size_t ret = 0; int i; uint entries_printed = 0; + int cumul_count = 0; remaining = total_samples; @@ -99,6 +100,7 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, child = rb_entry(node, struct callchain_node, rb_node); cumul = callchain_cumul_hits(child); remaining -= cumul; + cumul_count += callchain_cumul_counts(child); /* * The depth mask manages the output of pipes that show @@ -148,6 +150,12 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, if (!rem_sq_bracket) return ret; + if (callchain_param.value == CCVAL_COUNT && child && child->parent) { + rem_node.count = child->parent->children_count - cumul_count; + if (rem_node.count <= 0) + return ret; + } + new_depth_mask &= ~(1 << (depth - 1)); ret += ipchain__fprintf_graph(fp, &rem_node, &rem_hits, depth, new_depth_mask, 0, total_samples, diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index e390edd31504..717c58c1da58 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -83,6 +83,23 @@ static int parse_callchain_sort_key(const char *value) return -1; } +static int parse_callchain_value(const char *value) +{ + if (!strncmp(value, "percent", strlen(value))) { + callchain_param.value = CCVAL_PERCENT; + return 0; + } + if (!strncmp(value, "period", strlen(value))) { + callchain_param.value = CCVAL_PERIOD; + return 0; + } + if (!strncmp(value, "count", strlen(value))) { + callchain_param.value = CCVAL_COUNT; + return 0; + } + return -1; +} + static int __parse_callchain_report_opt(const char *arg, bool allow_record_opt) { @@ -106,7 +123,8 @@ __parse_callchain_report_opt(const char *arg, bool allow_record_opt) if (!parse_callchain_mode(tok) || !parse_callchain_order(tok) || - !parse_callchain_sort_key(tok)) { + !parse_callchain_sort_key(tok) || + !parse_callchain_value(tok)) { /* parsing ok - move on to the next */ try_stack_size = false; goto next; @@ -820,13 +838,27 @@ char *callchain_node__scnprintf_value(struct callchain_node *node, { double percent = 0.0; u64 period = callchain_cumul_hits(node); + unsigned count = callchain_cumul_counts(node); - if (callchain_param.mode == CHAIN_FOLDED) + if (callchain_param.mode == CHAIN_FOLDED) { period = node->hit; - if (total) - percent = period * 100.0 / total; + count = node->count; + } - scnprintf(bf, bfsize, "%.2f%%", percent); + switch (callchain_param.value) { + case CCVAL_PERIOD: + scnprintf(bf, bfsize, "%"PRIu64, period); + break; + case CCVAL_COUNT: + scnprintf(bf, bfsize, "%u", count); + break; + case CCVAL_PERCENT: + default: + if (total) + percent = period * 100.0 / total; + scnprintf(bf, bfsize, "%.2f%%", percent); + break; + } return bf; } @@ -835,13 +867,25 @@ int callchain_node__fprintf_value(struct callchain_node *node, { double percent = 0.0; u64 period = callchain_cumul_hits(node); + unsigned count = callchain_cumul_counts(node); - if (callchain_param.mode == CHAIN_FOLDED) + if (callchain_param.mode == CHAIN_FOLDED) { period = node->hit; - if (total) - percent = period * 100.0 / total; + count = node->count; + } - return percent_color_fprintf(fp, "%.2f%%", percent); + switch (callchain_param.value) { + case CCVAL_PERIOD: + return fprintf(fp, "%"PRIu64, period); + case CCVAL_COUNT: + return fprintf(fp, "%u", count); + case CCVAL_PERCENT: + default: + if (total) + percent = period * 100.0 / total; + return percent_color_fprintf(fp, "%.2f%%", percent); + } + return 0; } static void free_callchain_node(struct callchain_node *node) diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index cdb386d9ba02..47bc0c57f764 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -29,7 +29,8 @@ HELP_PAD "print_limit:\tmaximum number of call graph entry ()\n" \ HELP_PAD "order:\t\tcall graph order (caller|callee)\n" \ HELP_PAD "sort_key:\tcall graph sort key (function|address)\n" \ - HELP_PAD "branch:\t\tinclude last branch info to call graph (branch)\n" + HELP_PAD "branch:\t\tinclude last branch info to call graph (branch)\n" \ + HELP_PAD "value:\t\tcall graph value (percent|period|count)\n" enum perf_call_graph_mode { CALLCHAIN_NONE, @@ -81,6 +82,12 @@ enum chain_key { CCKEY_ADDRESS }; +enum chain_value { + CCVAL_PERCENT, + CCVAL_PERIOD, + CCVAL_COUNT, +}; + struct callchain_param { bool enabled; enum perf_call_graph_mode record_mode; @@ -93,6 +100,7 @@ struct callchain_param { bool order_set; enum chain_key key; bool branch_callstack; + enum chain_value value; }; extern struct callchain_param callchain_param; diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 47b1e36c7ea0..75759aebc7b8 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -21,7 +21,8 @@ struct callchain_param callchain_param = { .mode = CHAIN_GRAPH_ABS, .min_percent = 0.5, .order = ORDER_CALLEE, - .key = CCKEY_FUNCTION + .key = CCKEY_FUNCTION, + .value = CCVAL_PERCENT, }; /* From 18bb838129b08fb0009b1ba1dc2f748a9537ee89 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:42 +0900 Subject: [PATCH 33/37] perf hists browser: Factor out hist_browser__show_callchain_list() This function is to print a single callchain list entry. As this function will be used by other function, factor out to a separate function. Signed-off-by: Namhyung Kim Cc: Andi Kleen Cc: Brendan Gregg Cc: David Ahern Cc: Frederic Weisbecker Cc: Jiri Olsa Cc: Kan Liang Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-7-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/ui/browsers/hists.c | 72 +++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index 0b18857a36e8..0746d41d9efe 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -574,6 +574,44 @@ static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_u #define LEVEL_OFFSET_STEP 3 +static int hist_browser__show_callchain_list(struct hist_browser *browser, + struct callchain_node *node, + struct callchain_list *chain, + unsigned short row, u64 total, + bool need_percent, int offset, + print_callchain_entry_fn print, + struct callchain_print_arg *arg) +{ + char bf[1024], *alloc_str; + const char *str; + + if (arg->row_offset != 0) { + arg->row_offset--; + return 0; + } + + alloc_str = NULL; + str = callchain_list__sym_name(chain, bf, sizeof(bf), + browser->show_dso); + + if (need_percent) { + char buf[64]; + + callchain_node__scnprintf_value(node, buf, sizeof(buf), + total); + + if (asprintf(&alloc_str, "%s %s", buf, str) < 0) + str = "Not enough memory!"; + else + str = alloc_str; + } + + print(browser, chain, str, offset, row, arg); + + free(alloc_str); + return 1; +} + static int hist_browser__show_callchain(struct hist_browser *browser, struct rb_root *root, int level, unsigned short row, u64 total, @@ -598,8 +636,6 @@ static int hist_browser__show_callchain(struct hist_browser *browser, int extra_offset = 0; list_for_each_entry(chain, &child->val, list) { - char bf[1024], *alloc_str; - const char *str; bool was_first = first; if (first) @@ -608,34 +644,16 @@ static int hist_browser__show_callchain(struct hist_browser *browser, extra_offset = LEVEL_OFFSET_STEP; folded_sign = callchain_list__folded(chain); - if (arg->row_offset != 0) { - arg->row_offset--; - goto do_next; - } - alloc_str = NULL; - str = callchain_list__sym_name(chain, bf, sizeof(bf), - browser->show_dso); + row += hist_browser__show_callchain_list(browser, child, + chain, row, total, + was_first && need_percent, + offset + extra_offset, + print, arg); - if (was_first && need_percent) { - char buf[64]; - - callchain_node__scnprintf_value(child, buf, sizeof(buf), - total); - - if (asprintf(&alloc_str, "%s %s", buf, str) < 0) - str = "Not enough memory!"; - else - str = alloc_str; - } - - print(browser, chain, str, offset + extra_offset, row, arg); - - free(alloc_str); - - if (is_output_full(browser, ++row)) + if (is_output_full(browser, row)) goto out; -do_next: + if (folded_sign == '+') break; } From 4b3a3212233a042f48b7b8fedc64933e1ccd8643 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:43 +0900 Subject: [PATCH 34/37] perf hists browser: Support flat callchains The flat callchain mode is to print all chains in a single, simple hierarchy so make it easy to see. Currently perf report --tui doesn't show flat callchains properly. With flat callchains, only leaf nodes are added to the final rbtree so it should show entries in parent nodes. To do that, add parent_val list to struct callchain_node and show them along with the (normal) val list. For example, consider following callchains with '-g graph'. $ perf report -g graph - 39.93% swapper [kernel.vmlinux] [k] intel_idle intel_idle cpuidle_enter_state cpuidle_enter call_cpuidle - cpu_startup_entry 28.63% start_secondary - 11.30% rest_init start_kernel x86_64_start_reservations x86_64_start_kernel Before: $ perf report -g flat - 39.93% swapper [kernel.vmlinux] [k] intel_idle 28.63% start_secondary - 11.30% rest_init start_kernel x86_64_start_reservations x86_64_start_kernel After: $ perf report -g flat - 39.93% swapper [kernel.vmlinux] [k] intel_idle - 28.63% intel_idle cpuidle_enter_state cpuidle_enter call_cpuidle cpu_startup_entry start_secondary - 11.30% intel_idle cpuidle_enter_state cpuidle_enter call_cpuidle cpu_startup_entry start_kernel x86_64_start_reservations x86_64_start_kernel Signed-off-by: Namhyung Kim Tested-by: Arnaldo Carvalho de Melo Tested-by: Brendan Gregg Cc: Andi Kleen Cc: David Ahern Cc: Frederic Weisbecker Cc: Jiri Olsa Cc: Kan Liang Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-8-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/ui/browsers/hists.c | 122 ++++++++++++++++++++++++++++++++- tools/perf/util/callchain.c | 44 ++++++++++++ tools/perf/util/callchain.h | 2 + 3 files changed, 166 insertions(+), 2 deletions(-) diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index 0746d41d9efe..c44af461a68f 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -178,12 +178,44 @@ static int callchain_node__count_rows_rb_tree(struct callchain_node *node) return n; } +static int callchain_node__count_flat_rows(struct callchain_node *node) +{ + struct callchain_list *chain; + char folded_sign = 0; + int n = 0; + + list_for_each_entry(chain, &node->parent_val, list) { + if (!folded_sign) { + /* only check first chain list entry */ + folded_sign = callchain_list__folded(chain); + if (folded_sign == '+') + return 1; + } + n++; + } + + list_for_each_entry(chain, &node->val, list) { + if (!folded_sign) { + /* node->parent_val list might be empty */ + folded_sign = callchain_list__folded(chain); + if (folded_sign == '+') + return 1; + } + n++; + } + + return n; +} + static int callchain_node__count_rows(struct callchain_node *node) { struct callchain_list *chain; bool unfolded = false; int n = 0; + if (callchain_param.mode == CHAIN_FLAT) + return callchain_node__count_flat_rows(node); + list_for_each_entry(chain, &node->val, list) { ++n; unfolded = chain->unfolded; @@ -263,7 +295,7 @@ static void callchain_node__init_have_children(struct callchain_node *node, chain = list_entry(node->val.next, struct callchain_list, list); chain->has_children = has_sibling; - if (!list_empty(&node->val)) { + if (node->val.next != node->val.prev) { chain = list_entry(node->val.prev, struct callchain_list, list); chain->has_children = !RB_EMPTY_ROOT(&node->rb_root); } @@ -279,6 +311,8 @@ static void callchain__init_have_children(struct rb_root *root) for (nd = rb_first(root); nd; nd = rb_next(nd)) { struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); callchain_node__init_have_children(node, has_sibling); + if (callchain_param.mode == CHAIN_FLAT) + callchain_node__make_parent_list(node); } } @@ -612,6 +646,83 @@ static int hist_browser__show_callchain_list(struct hist_browser *browser, return 1; } +static int hist_browser__show_callchain_flat(struct hist_browser *browser, + struct rb_root *root, + unsigned short row, u64 total, + print_callchain_entry_fn print, + struct callchain_print_arg *arg, + check_output_full_fn is_output_full) +{ + struct rb_node *node; + int first_row = row, offset = LEVEL_OFFSET_STEP; + bool need_percent; + + node = rb_first(root); + need_percent = node && rb_next(node); + + while (node) { + struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); + struct rb_node *next = rb_next(node); + struct callchain_list *chain; + char folded_sign = ' '; + int first = true; + int extra_offset = 0; + + list_for_each_entry(chain, &child->parent_val, list) { + bool was_first = first; + + if (first) + first = false; + else if (need_percent) + extra_offset = LEVEL_OFFSET_STEP; + + folded_sign = callchain_list__folded(chain); + + row += hist_browser__show_callchain_list(browser, child, + chain, row, total, + was_first && need_percent, + offset + extra_offset, + print, arg); + + if (is_output_full(browser, row)) + goto out; + + if (folded_sign == '+') + goto next; + } + + list_for_each_entry(chain, &child->val, list) { + bool was_first = first; + + if (first) + first = false; + else if (need_percent) + extra_offset = LEVEL_OFFSET_STEP; + + folded_sign = callchain_list__folded(chain); + + row += hist_browser__show_callchain_list(browser, child, + chain, row, total, + was_first && need_percent, + offset + extra_offset, + print, arg); + + if (is_output_full(browser, row)) + goto out; + + if (folded_sign == '+') + break; + } + +next: + if (is_output_full(browser, row)) + break; + node = next; + } +out: + return row - first_row; +} + static int hist_browser__show_callchain(struct hist_browser *browser, struct rb_root *root, int level, unsigned short row, u64 total, @@ -864,10 +975,17 @@ static int hist_browser__show_entry(struct hist_browser *browser, total = entry->stat.period; } - printed += hist_browser__show_callchain(browser, + if (callchain_param.mode == CHAIN_FLAT) { + printed += hist_browser__show_callchain_flat(browser, + &entry->sorted_chain, row, total, + hist_browser__show_callchain_entry, &arg, + hist_browser__check_output_full); + } else { + printed += hist_browser__show_callchain(browser, &entry->sorted_chain, 1, row, total, hist_browser__show_callchain_entry, &arg, hist_browser__check_output_full); + } if (arg.is_current_entry) browser->he_selection = entry; diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 717c58c1da58..fc3b1e0d09ee 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -387,6 +387,7 @@ create_child(struct callchain_node *parent, bool inherit_children) } new->parent = parent; INIT_LIST_HEAD(&new->val); + INIT_LIST_HEAD(&new->parent_val); if (inherit_children) { struct rb_node *n; @@ -894,6 +895,11 @@ static void free_callchain_node(struct callchain_node *node) struct callchain_node *child; struct rb_node *n; + list_for_each_entry_safe(list, tmp, &node->parent_val, list) { + list_del(&list->list); + free(list); + } + list_for_each_entry_safe(list, tmp, &node->val, list) { list_del(&list->list); free(list); @@ -917,3 +923,41 @@ void free_callchain(struct callchain_root *root) free_callchain_node(&root->node); } + +int callchain_node__make_parent_list(struct callchain_node *node) +{ + struct callchain_node *parent = node->parent; + struct callchain_list *chain, *new; + LIST_HEAD(head); + + while (parent) { + list_for_each_entry_reverse(chain, &parent->val, list) { + new = malloc(sizeof(*new)); + if (new == NULL) + goto out; + *new = *chain; + new->has_children = false; + list_add_tail(&new->list, &head); + } + parent = parent->parent; + } + + list_for_each_entry_safe_reverse(chain, new, &head, list) + list_move_tail(&chain->list, &node->parent_val); + + if (!list_empty(&node->parent_val)) { + chain = list_first_entry(&node->parent_val, struct callchain_list, list); + chain->has_children = rb_prev(&node->rb_node) || rb_next(&node->rb_node); + + chain = list_first_entry(&node->val, struct callchain_list, list); + chain->has_children = false; + } + return 0; + +out: + list_for_each_entry_safe(chain, new, &head, list) { + list_del(&chain->list); + free(chain); + } + return -ENOMEM; +} diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index 47bc0c57f764..6e9b5f2099e1 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -56,6 +56,7 @@ enum chain_order { struct callchain_node { struct callchain_node *parent; struct list_head val; + struct list_head parent_val; struct rb_node rb_node_in; /* to insert nodes in an rbtree */ struct rb_node rb_node; /* to sort nodes in an output tree */ struct rb_root rb_root_in; /* input tree of children */ @@ -251,5 +252,6 @@ int callchain_node__fprintf_value(struct callchain_node *node, FILE *fp, u64 total); void free_callchain(struct callchain_root *root); +int callchain_node__make_parent_list(struct callchain_node *node); #endif /* __PERF_CALLCHAIN_H */ From 8c430a34869946f1f5852f02d910ceef80040be5 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:44 +0900 Subject: [PATCH 35/37] perf hists browser: Support folded callchains The folded callchain mode prints all chains in a single line. Currently perf report --tui doesn't support folded callchains. Like flat callchains, only leaf nodes are added to the final rbtree so it should show entries in parent nodes. To do that, add flat_val list to struct callchain_node and show them along with the (normal) val list. For example, folded callchain looks like below: $ perf report -g folded --tui Samples: 234 of event 'cycles:pp', Event count (approx.): 32605268 Overhead Command Shared Object Symbol - 39.93% swapper [kernel.vmlinux] [k] intel_idle + 28.63% intel_idle; cpuidle_enter_state; cpuidle_enter; ... + 11.30% intel_idle; cpuidle_enter_state; cpuidle_enter; ... Signed-off-by: Namhyung Kim Tested-by: Arnaldo Carvalho de Melo Tested-by: Brendan Gregg Cc: Andi Kleen Cc: David Ahern Cc: Frederic Weisbecker Cc: Jiri Olsa Cc: Kan Liang Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-9-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/ui/browsers/hists.c | 125 ++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index c44af461a68f..a211b7b6a81e 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -207,6 +207,11 @@ static int callchain_node__count_flat_rows(struct callchain_node *node) return n; } +static int callchain_node__count_folded_rows(struct callchain_node *node __maybe_unused) +{ + return 1; +} + static int callchain_node__count_rows(struct callchain_node *node) { struct callchain_list *chain; @@ -215,6 +220,8 @@ static int callchain_node__count_rows(struct callchain_node *node) if (callchain_param.mode == CHAIN_FLAT) return callchain_node__count_flat_rows(node); + else if (callchain_param.mode == CHAIN_FOLDED) + return callchain_node__count_folded_rows(node); list_for_each_entry(chain, &node->val, list) { ++n; @@ -311,7 +318,8 @@ static void callchain__init_have_children(struct rb_root *root) for (nd = rb_first(root); nd; nd = rb_next(nd)) { struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); callchain_node__init_have_children(node, has_sibling); - if (callchain_param.mode == CHAIN_FLAT) + if (callchain_param.mode == CHAIN_FLAT || + callchain_param.mode == CHAIN_FOLDED) callchain_node__make_parent_list(node); } } @@ -723,6 +731,116 @@ out: return row - first_row; } +static char *hist_browser__folded_callchain_str(struct hist_browser *browser, + struct callchain_list *chain, + char *value_str, char *old_str) +{ + char bf[1024]; + const char *str; + char *new; + + str = callchain_list__sym_name(chain, bf, sizeof(bf), + browser->show_dso); + if (old_str) { + if (asprintf(&new, "%s%s%s", old_str, + symbol_conf.field_sep ?: ";", str) < 0) + new = NULL; + } else { + if (value_str) { + if (asprintf(&new, "%s %s", value_str, str) < 0) + new = NULL; + } else { + if (asprintf(&new, "%s", str) < 0) + new = NULL; + } + } + return new; +} + +static int hist_browser__show_callchain_folded(struct hist_browser *browser, + struct rb_root *root, + unsigned short row, u64 total, + print_callchain_entry_fn print, + struct callchain_print_arg *arg, + check_output_full_fn is_output_full) +{ + struct rb_node *node; + int first_row = row, offset = LEVEL_OFFSET_STEP; + bool need_percent; + + node = rb_first(root); + need_percent = node && rb_next(node); + + while (node) { + struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); + struct rb_node *next = rb_next(node); + struct callchain_list *chain, *first_chain = NULL; + int first = true; + char *value_str = NULL, *value_str_alloc = NULL; + char *chain_str = NULL, *chain_str_alloc = NULL; + + if (arg->row_offset != 0) { + arg->row_offset--; + goto next; + } + + if (need_percent) { + char buf[64]; + + callchain_node__scnprintf_value(child, buf, sizeof(buf), total); + if (asprintf(&value_str, "%s", buf) < 0) { + value_str = (char *)"<...>"; + goto do_print; + } + value_str_alloc = value_str; + } + + list_for_each_entry(chain, &child->parent_val, list) { + chain_str = hist_browser__folded_callchain_str(browser, + chain, value_str, chain_str); + if (first) { + first = false; + first_chain = chain; + } + + if (chain_str == NULL) { + chain_str = (char *)"Not enough memory!"; + goto do_print; + } + + chain_str_alloc = chain_str; + } + + list_for_each_entry(chain, &child->val, list) { + chain_str = hist_browser__folded_callchain_str(browser, + chain, value_str, chain_str); + if (first) { + first = false; + first_chain = chain; + } + + if (chain_str == NULL) { + chain_str = (char *)"Not enough memory!"; + goto do_print; + } + + chain_str_alloc = chain_str; + } + +do_print: + print(browser, first_chain, chain_str, offset, row++, arg); + free(value_str_alloc); + free(chain_str_alloc); + +next: + if (is_output_full(browser, row)) + break; + node = next; + } + + return row - first_row; +} + static int hist_browser__show_callchain(struct hist_browser *browser, struct rb_root *root, int level, unsigned short row, u64 total, @@ -980,6 +1098,11 @@ static int hist_browser__show_entry(struct hist_browser *browser, &entry->sorted_chain, row, total, hist_browser__show_callchain_entry, &arg, hist_browser__check_output_full); + } else if (callchain_param.mode == CHAIN_FOLDED) { + printed += hist_browser__show_callchain_folded(browser, + &entry->sorted_chain, row, total, + hist_browser__show_callchain_entry, &arg, + hist_browser__check_output_full); } else { printed += hist_browser__show_callchain(browser, &entry->sorted_chain, 1, row, total, From 3cd99dfd1c87067fb28a19fee76500aed56d7c8f Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:45 +0900 Subject: [PATCH 36/37] perf ui/gtk: Support flat callchains The flat callchain mode is to print all chains in a simple flat hierarchy so make it easy to see. Currently perf report --gtk doesn't show flat callchains properly. With flat callchains, only leaf nodes are added to the final rbtree so it should show entries in parent nodes. To do that, add parent_val list to struct callchain_node and show them along with the (normal) val list. See the previous commit on TUI support for more information. Signed-off-by: Namhyung Kim Tested-by: Brendan Gregg Cc: Andi Kleen Cc: David Ahern Cc: Frederic Weisbecker Cc: Jiri Olsa Cc: Kan Liang Cc: Pekka Enberg Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-10-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/ui/gtk/hists.c | 80 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index cff7bb9d9632..0b24cd6d38a4 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -89,8 +89,71 @@ void perf_gtk__init_hpp(void) perf_gtk__hpp_color_overhead_acc; } -static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, - GtkTreeIter *parent, int col, u64 total) +static void perf_gtk__add_callchain_flat(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) +{ + struct rb_node *nd; + bool has_single_node = (rb_first(root) == rb_last(root)); + + for (nd = rb_first(root); nd; nd = rb_next(nd)) { + struct callchain_node *node; + struct callchain_list *chain; + GtkTreeIter iter, new_parent; + bool need_new_parent; + + node = rb_entry(nd, struct callchain_node, rb_node); + + new_parent = *parent; + need_new_parent = !has_single_node; + + callchain_node__make_parent_list(node); + + list_for_each_entry(chain, &node->parent_val, list) { + char buf[128]; + + gtk_tree_store_append(store, &iter, &new_parent); + + callchain_node__scnprintf_value(node, buf, sizeof(buf), total); + gtk_tree_store_set(store, &iter, 0, buf, -1); + + callchain_list__sym_name(chain, buf, sizeof(buf), false); + gtk_tree_store_set(store, &iter, col, buf, -1); + + if (need_new_parent) { + /* + * Only show the top-most symbol in a callchain + * if it's not the only callchain. + */ + new_parent = iter; + need_new_parent = false; + } + } + + list_for_each_entry(chain, &node->val, list) { + char buf[128]; + + gtk_tree_store_append(store, &iter, &new_parent); + + callchain_node__scnprintf_value(node, buf, sizeof(buf), total); + gtk_tree_store_set(store, &iter, 0, buf, -1); + + callchain_list__sym_name(chain, buf, sizeof(buf), false); + gtk_tree_store_set(store, &iter, col, buf, -1); + + if (need_new_parent) { + /* + * Only show the top-most symbol in a callchain + * if it's not the only callchain. + */ + new_parent = iter; + need_new_parent = false; + } + } + } +} + +static void perf_gtk__add_callchain_graph(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) { struct rb_node *nd; bool has_single_node = (rb_first(root) == rb_last(root)); @@ -134,11 +197,20 @@ static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, child_total = total; /* Now 'iter' contains info of the last callchain_list */ - perf_gtk__add_callchain(&node->rb_root, store, &iter, col, - child_total); + perf_gtk__add_callchain_graph(&node->rb_root, store, &iter, col, + child_total); } } +static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) +{ + if (callchain_param.mode == CHAIN_FLAT) + perf_gtk__add_callchain_flat(root, store, parent, col, total); + else + perf_gtk__add_callchain_graph(root, store, parent, col, total); +} + static void on_row_activated(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col __maybe_unused, gpointer user_data __maybe_unused) From 2c6caff2b26fde8f3f87183f8c97f2cebfdbcb98 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 9 Nov 2015 14:45:46 +0900 Subject: [PATCH 37/37] perf ui/gtk: Support folded callchains The folded callchain mode is to print all chains in a single line. Currently perf report --gtk doesn't support folded callchains. Like flat callchains, only leaf nodes are added to the final rbtree so it should show entries in parent nodes. Signed-off-by: Namhyung Kim Tested-by: Arnaldo Carvalho de Melo Tested-by: Brendan Gregg Cc: Andi Kleen Cc: David Ahern Cc: Frederic Weisbecker Cc: Jiri Olsa Cc: Kan Liang Cc: Pekka Enberg Cc: Peter Zijlstra Link: http://lkml.kernel.org/r/1447047946-1691-11-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/ui/gtk/hists.c | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index 0b24cd6d38a4..467717276ab6 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -152,6 +152,66 @@ static void perf_gtk__add_callchain_flat(struct rb_root *root, GtkTreeStore *sto } } +static void perf_gtk__add_callchain_folded(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) +{ + struct rb_node *nd; + + for (nd = rb_first(root); nd; nd = rb_next(nd)) { + struct callchain_node *node; + struct callchain_list *chain; + GtkTreeIter iter; + char buf[64]; + char *str, *str_alloc = NULL; + bool first = true; + + node = rb_entry(nd, struct callchain_node, rb_node); + + callchain_node__make_parent_list(node); + + list_for_each_entry(chain, &node->parent_val, list) { + char name[1024]; + + callchain_list__sym_name(chain, name, sizeof(name), false); + + if (asprintf(&str, "%s%s%s", + first ? "" : str_alloc, + first ? "" : symbol_conf.field_sep ?: "; ", + name) < 0) + return; + + first = false; + free(str_alloc); + str_alloc = str; + } + + list_for_each_entry(chain, &node->val, list) { + char name[1024]; + + callchain_list__sym_name(chain, name, sizeof(name), false); + + if (asprintf(&str, "%s%s%s", + first ? "" : str_alloc, + first ? "" : symbol_conf.field_sep ?: "; ", + name) < 0) + return; + + first = false; + free(str_alloc); + str_alloc = str; + } + + gtk_tree_store_append(store, &iter, parent); + + callchain_node__scnprintf_value(node, buf, sizeof(buf), total); + gtk_tree_store_set(store, &iter, 0, buf, -1); + + gtk_tree_store_set(store, &iter, col, str, -1); + + free(str_alloc); + } +} + static void perf_gtk__add_callchain_graph(struct rb_root *root, GtkTreeStore *store, GtkTreeIter *parent, int col, u64 total) { @@ -207,6 +267,8 @@ static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, { if (callchain_param.mode == CHAIN_FLAT) perf_gtk__add_callchain_flat(root, store, parent, col, total); + else if (callchain_param.mode == CHAIN_FOLDED) + perf_gtk__add_callchain_folded(root, store, parent, col, total); else perf_gtk__add_callchain_graph(root, store, parent, col, total); }